diff --git a/includes/admin/importers/class-wc-product-csv-importer-controller.php b/includes/admin/importers/class-wc-product-csv-importer-controller.php index c632c89fb2a..8230fbcf188 100644 --- a/includes/admin/importers/class-wc-product-csv-importer-controller.php +++ b/includes/admin/importers/class-wc-product-csv-importer-controller.php @@ -445,13 +445,14 @@ class WC_Product_CSV_Importer_Controller { array( $this, 'sanitize_special_column_name_regex' ), apply_filters( 'woocommerce_csv_product_import_mapping_special_columns', array( - 'attributes:name' => __( 'Attribute %d name', 'woocommerce' ), - 'attributes:value' => __( 'Attribute %d value(s)', 'woocommerce' ), - 'attributes:visible' => __( 'Attribute %d visible', 'woocommerce' ), - 'attributes:default' => __( 'Attribute %d default', 'woocommerce' ), - 'downloads:name' => __( 'Download %d name', 'woocommerce' ), - 'downloads:url' => __( 'Download %d URL', 'woocommerce' ), - 'meta:' => __( 'Meta: %s', 'woocommerce' ), + 'attributes:name' => __( 'Attribute %d name', 'woocommerce' ), + 'attributes:value' => __( 'Attribute %d value(s)', 'woocommerce' ), + 'attributes:visible' => __( 'Attribute %d visible', 'woocommerce' ), + 'attributes:taxonomy' => __( 'Attribute %d global', 'woocommerce' ), + 'attributes:default' => __( 'Attribute %d default', 'woocommerce' ), + 'downloads:name' => __( 'Download %d name', 'woocommerce' ), + 'downloads:url' => __( 'Download %d URL', 'woocommerce' ), + 'meta:' => __( 'Meta: %s', 'woocommerce' ), ) ) ); @@ -568,10 +569,11 @@ class WC_Product_CSV_Importer_Controller { 'attributes' => array( 'name' => __( 'Attributes', 'woocommerce' ), 'options' => array( - 'attributes:name' . $index => __( 'Attribute name', 'woocommerce' ), - 'attributes:value' . $index => __( 'Attribute value(s)', 'woocommerce' ), - 'attributes:visible' . $index => __( 'Attribute visibility', 'woocommerce' ), - 'attributes:default' . $index => __( 'Default attribute', 'woocommerce' ), + 'attributes:name' . $index => __( 'Attribute name', 'woocommerce' ), + 'attributes:value' . $index => __( 'Attribute value(s)', 'woocommerce' ), + 'attributes:taxonomy' . $index => __( 'Is a global attribute?', 'woocommerce' ), + 'attributes:visible' . $index => __( 'Attribute visibility', 'woocommerce' ), + 'attributes:default' . $index => __( 'Default attribute', 'woocommerce' ), ), ), 'meta:' . $meta => __( 'Import as meta', 'woocommerce' ), diff --git a/includes/export/class-wc-product-csv-exporter.php b/includes/export/class-wc-product-csv-exporter.php index 1584fee4eda..b5aa0ad3636 100644 --- a/includes/export/class-wc-product-csv-exporter.php +++ b/includes/export/class-wc-product-csv-exporter.php @@ -462,9 +462,10 @@ class WC_Product_CSV_Exporter extends WC_CSV_Batch_Exporter { if ( count( $attributes ) ) { $i = 1; foreach ( $attributes as $attribute_name => $attribute ) { - $this->column_names[ 'attributes:name' . $i ] = sprintf( __( 'Attribute %d name', 'woocommerce' ), $i ); - $this->column_names[ 'attributes:value' . $i ] = sprintf( __( 'Attribute %d value(s)', 'woocommerce' ), $i ); - $this->column_names[ 'attributes:visible' . $i ] = sprintf( __( 'Attribute %d visible', 'woocommerce' ), $i ); + $this->column_names[ 'attributes:name' . $i ] = sprintf( __( 'Attribute %d name', 'woocommerce' ), $i ); + $this->column_names[ 'attributes:value' . $i ] = sprintf( __( 'Attribute %d value(s)', 'woocommerce' ), $i ); + $this->column_names[ 'attributes:visible' . $i ] = sprintf( __( 'Attribute %d visible', 'woocommerce' ), $i ); + $this->column_names[ 'attributes:taxonomy' . $i ] = sprintf( __( 'Attribute %d global', 'woocommerce' ), $i ); if ( is_a( $attribute, 'WC_Product_Attribute' ) ) { $row[ 'attributes:name' . $i ] = wc_attribute_label( $attribute->get_name(), $product ); @@ -477,9 +478,11 @@ class WC_Product_CSV_Exporter extends WC_CSV_Batch_Exporter { $values[] = $term->name; } - $row[ 'attributes:value' . $i ] = implode( ', ', $values ); + $row[ 'attributes:value' . $i ] = implode( ', ', $values ); + $row[ 'attributes:taxonomy' . $i ] = 1; } else { - $row[ 'attributes:value' . $i ] = implode( ', ', $attribute->get_options() ); + $row[ 'attributes:value' . $i ] = implode( ', ', $attribute->get_options() ); + $row[ 'attributes:taxonomy' . $i ] = 0; } $row[ 'attributes:visible' . $i ] = $attribute->get_visible(); @@ -488,9 +491,11 @@ class WC_Product_CSV_Exporter extends WC_CSV_Batch_Exporter { if ( 0 === strpos( $attribute_name, 'pa_' ) ) { $option_term = get_term_by( 'slug', $attribute, $attribute_name ); - $row[ 'attributes:value' . $i ] = $option_term && ! is_wp_error( $option_term ) ? $option_term->name : $attribute; + $row[ 'attributes:value' . $i ] = $option_term && ! is_wp_error( $option_term ) ? $option_term->name : $attribute; + $row[ 'attributes:taxonomy' . $i ] = 1; } else { - $row[ 'attributes:value' . $i ] = $attribute; + $row[ 'attributes:value' . $i ] = $attribute; + $row[ 'attributes:taxonomy' . $i ] = 0; } $row[ 'attributes:visible' . $i ] = ''; diff --git a/includes/import/abstract-wc-product-importer.php b/includes/import/abstract-wc-product-importer.php index 4fb425c9b8f..5539ec1c4d7 100644 --- a/includes/import/abstract-wc-product-importer.php +++ b/includes/import/abstract-wc-product-importer.php @@ -275,8 +275,12 @@ abstract class WC_Product_Importer implements WC_Importer_Interface { $default_attributes = array(); foreach ( $data['raw_attributes'] as $position => $attribute ) { + $attribute_id = 0; + // Get ID if is a global attribute. - $attribute_id = wc_attribute_taxonomy_id_by_name( $attribute['name'] ); + if ( ! empty( $attribute['taxonomy'] ) ) { + $attribute_id = $this->get_attribute_taxonomy_id_by_name( $attribute['name'] ); + } // Set attribute visibility. if ( isset( $attribute['visible'] ) ) { @@ -425,8 +429,12 @@ abstract class WC_Product_Importer implements WC_Importer_Interface { $require_save = false; foreach ( $attributes as $attribute ) { + $attribute_id = 0; + // Get ID if is a global attribute. - $attribute_id = wc_attribute_taxonomy_id_by_name( $attribute['name'] ); + if ( ! empty( $attribute['taxonomy'] ) ) { + $attribute_id = $this->get_attribute_taxonomy_id_by_name( $attribute['name'] ); + } if ( $attribute_id ) { $attribute_name = wc_attribute_taxonomy_name_by_id( $attribute_id ); @@ -527,4 +535,72 @@ abstract class WC_Product_Importer implements WC_Importer_Interface { return $id; } + + /** + * Get attribute taxonomy ID by name. + * If does not exists register a new attribute. + * + * @param string $name Attribute name. + * @return int + */ + protected function get_attribute_taxonomy_id_by_name( $name ) { + global $wpdb; + + // Check if exists. + if ( $attribute_id = wc_attribute_taxonomy_id_by_name( $name ) ) { + return $attribute_id; + } + + // Register new attribute. + $slug = wc_sanitize_taxonomy_name( $name ); + $args = array( + 'attribute_label' => $name, + 'attribute_name' => wc_sanitize_taxonomy_name( $name ), + 'attribute_type' => 'select', + 'attribute_orderby' => 'menu_order', + 'attribute_public' => 0, + ); + + // Validate attribute. + if ( strlen( $slug ) >= 28 ) { + throw new Exception( sprintf( __( 'Slug "%s" is too long (28 characters max). Shorten it, please.', 'woocommerce' ), $slug ), 400 ); + } elseif ( wc_check_if_attribute_name_is_reserved( $slug ) ) { + throw new Exception( sprintf( __( 'Slug "%s" is not allowed because it is a reserved term. Change it, please.', 'woocommerce' ), $slug ), 400 ); + } elseif ( $new_data && taxonomy_exists( wc_attribute_taxonomy_name( $slug ) ) ) { + throw new Exception( sprintf( __( 'Slug "%s" is already in use. Change it, please.', 'woocommerce' ), $slug ), 400 ); + } + + $result = $wpdb->insert( + $wpdb->prefix . 'woocommerce_attribute_taxonomies', + $args, + array( '%s', '%s', '%s', '%s', '%d' ) + ); + + // Pass errors. + if ( is_wp_error( $result ) ) { + throw new Exception( $result->get_error_message(), 400 ); + } + + // Delete transient. + delete_transient( 'wc_attribute_taxonomies' ); + + // Register as taxonomy while importing. + $taxonomy_data = array( + 'labels' => array( + 'name' => $name, + ), + ); + register_taxonomy( wc_attribute_taxonomy_name( $slug ), array( 'product' ), $taxonomy_data ); + + // Set product attributes global. + global $wc_product_attributes; + $wc_product_attributes = array(); + foreach ( wc_get_attribute_taxonomies() as $tax ) { + if ( $name = wc_attribute_taxonomy_name( $tax->attribute_name ) ) { + $wc_product_attributes[ $name ] = $tax; + } + } + + return $wpdb->insert_id; + } } diff --git a/includes/import/class-wc-product-csv-importer.php b/includes/import/class-wc-product-csv-importer.php index 086d9677ae9..901675efaa9 100644 --- a/includes/import/class-wc-product-csv-importer.php +++ b/includes/import/class-wc-product-csv-importer.php @@ -355,10 +355,11 @@ class WC_Product_CSV_Importer extends WC_Product_Importer { * Match special column names. */ $regex_match_data_formatting = array( - '/attributes:value*/' => array( $this, 'parse_comma_field' ), - '/attributes:visible*/' => array( $this, 'parse_bool_field' ), - '/downloads:url*/' => 'esc_url', - '/meta:*/' => 'wp_kses_post', // Allow some HTML in meta fields. + '/attributes:value*/' => array( $this, 'parse_comma_field' ), + '/attributes:visible*/' => array( $this, 'parse_bool_field' ), + '/attributes:taxonomy*/' => array( $this, 'parse_bool_field' ), + '/downloads:url*/' => 'esc_url', + '/meta:*/' => 'wp_kses_post', // Allow some HTML in meta fields. ); $callbacks = array(); @@ -473,6 +474,10 @@ class WC_Product_CSV_Importer extends WC_Product_Importer { $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 ] );