diff --git a/includes/admin/class-wc-admin-importers.php b/includes/admin/class-wc-admin-importers.php index df19a0f5af8..f40ac859897 100644 --- a/includes/admin/class-wc-admin-importers.php +++ b/includes/admin/class-wc-admin-importers.php @@ -203,11 +203,11 @@ class WC_Admin_Importers { $file = wc_clean( $_POST['file'] ); $params = array( - 'start_pos' => isset( $_POST['position'] ) ? absint( $_POST['position'] ) : 0, - 'mapping' => isset( $_POST['mapping'] ) ? (array) $_POST['mapping'] : array(), + 'start_pos' => isset( $_POST['position'] ) ? absint( $_POST['position'] ) : 0, + 'mapping' => isset( $_POST['mapping'] ) ? (array) $_POST['mapping'] : array(), 'update_existing' => isset( $_POST['update_existing'] ) ? (bool) $_POST['update_existing'] : false, - 'lines' => apply_filters( 'woocommerce_product_import_batch_size', 10 ), - 'parse' => true, + 'lines' => apply_filters( 'woocommerce_product_import_batch_size', 10 ), + 'parse' => true, ); // Log failures. 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 279edcbb33a..47e7ffc0cdc 100644 --- a/includes/admin/importers/class-wc-product-csv-importer-controller.php +++ b/includes/admin/importers/class-wc-product-csv-importer-controller.php @@ -127,11 +127,11 @@ class WC_Product_CSV_Importer_Controller { } $params = array( - 'step' => $keys[ $step_index + 1 ], - 'file' => $this->file, - 'delimiter' => $this->delimiter, + 'step' => $keys[ $step_index + 1 ], + 'file' => $this->file, + 'delimiter' => $this->delimiter, 'update_existing' => $this->update_existing, - '_wpnonce' => wp_create_nonce( 'woocommerce-csv-importer' ), // wp_nonce_url() escapes & to & breaking redirects. + '_wpnonce' => wp_create_nonce( 'woocommerce-csv-importer' ), // wp_nonce_url() escapes & to & breaking redirects. ); return add_query_arg( $params ); @@ -311,9 +311,9 @@ class WC_Product_CSV_Importer_Controller { } wp_localize_script( 'wc-product-import', 'wc_product_import_params', array( - 'import_nonce' => wp_create_nonce( 'wc-product-import' ), - 'mapping' => $mapping, - 'file' => $this->file, + 'import_nonce' => wp_create_nonce( 'wc-product-import' ), + 'mapping' => $mapping, + 'file' => $this->file, 'update_existing' => $this->update_existing, ) ); wp_enqueue_script( 'wc-product-import' ); @@ -323,7 +323,6 @@ class WC_Product_CSV_Importer_Controller { /** * Done step. - * @todo Make this better. */ protected function done() { $imported = isset( $_GET['products-imported'] ) ? absint( $_GET['products-imported'] ) : 0; diff --git a/includes/export/abstract-wc-csv-exporter.php b/includes/export/abstract-wc-csv-exporter.php index e857ea51fb8..0671311dbaa 100644 --- a/includes/export/abstract-wc-csv-exporter.php +++ b/includes/export/abstract-wc-csv-exporter.php @@ -343,13 +343,14 @@ abstract class WC_CSV_Exporter { public function format_data( $data ) { if ( ! is_scalar( $data ) ) { if ( is_a( $data, 'WC_Datetime' ) ) { - $data = $data->date( DATE_ATOM ); - } elseif ( is_bool( $data ) ) { - $data = $data ? 1 : 0; + $data = $data->date( 'Y-m-d G:i:s' ); } else { $data = ''; // Not supported. } + } elseif ( is_bool( $data ) ) { + $data = $data ? 1 : 0; } + $data = (string) urldecode( $data ); $encoding = mb_detect_encoding( $data, 'UTF-8, ISO-8859-1', true ); $data = 'UTF-8' === $encoding ? $data : utf8_encode( $data ); diff --git a/includes/export/class-wc-product-csv-exporter.php b/includes/export/class-wc-product-csv-exporter.php index aa4d8bcf3ee..bb151bc98ee 100644 --- a/includes/export/class-wc-product-csv-exporter.php +++ b/includes/export/class-wc-product-csv-exporter.php @@ -374,7 +374,7 @@ class WC_Product_CSV_Exporter extends WC_CSV_Batch_Exporter { } /** - * Get download_expiry value. + * Get type value. * * @since 3.1.0 * @param WC_Product $product diff --git a/includes/import/abstract-wc-product-importer.php b/includes/import/abstract-wc-product-importer.php index e82a6c40f68..75abf245630 100644 --- a/includes/import/abstract-wc-product-importer.php +++ b/includes/import/abstract-wc-product-importer.php @@ -684,7 +684,7 @@ abstract class WC_Product_Importer implements WC_Importer_Interface { if ( $attribute_id ) { $attribute_name = wc_attribute_taxonomy_name_by_id( $attribute_id ); } else { - $attribute_name = sanitize_title( $attribute['name']); + $attribute_name = sanitize_title( $attribute['name'] ); } if ( ! isset( $parent_attributes[ $attribute_name ] ) || ! $parent_attributes[ $attribute_name ]->get_variation() ) { @@ -739,7 +739,7 @@ abstract class WC_Product_Importer implements WC_Importer_Interface { if ( $attribute_id ) { $attribute_name = wc_attribute_taxonomy_name_by_id( $attribute_id ); } else { - $attribute_name = sanitize_title( $attribute['name']); + $attribute_name = sanitize_title( $attribute['name'] ); } // Check if attribute handle variations. diff --git a/tests/unit-tests/exporter/product.php b/tests/unit-tests/exporter/product.php new file mode 100644 index 00000000000..00dba2c2bd0 --- /dev/null +++ b/tests/unit-tests/exporter/product.php @@ -0,0 +1,147 @@ +plugin_dir . '/includes/export/class-wc-product-csv-exporter.php'; + } + + /** + * Test escape_data to prevent regressions that could open security holes. + * @since 3.1.0 + */ + public function test_escape_data() { + $exporter = new WC_Product_CSV_Exporter(); + + $data = "=cmd|' /C calc'!A0"; + $this->assertEquals( "'=cmd|' /C calc'!A0", $exporter->escape_data( $data ) ); + + $data = "+cmd|' /C calc'!A0"; + $this->assertEquals( "'+cmd|' /C calc'!A0", $exporter->escape_data( $data ) ); + + $data = "-cmd|' /C calc'!A0"; + $this->assertEquals( "'-cmd|' /C calc'!A0", $exporter->escape_data( $data ) ); + + $data = "@cmd|' /C calc'!A0"; + $this->assertEquals( "'@cmd|' /C calc'!A0", $exporter->escape_data( $data ) ); + } + + /** + * Test format_data. + * @since 3.1.0 + */ + public function test_format_data() { + $exporter = new WC_Product_CSV_Exporter(); + + $data = "test"; + $this->assertEquals( "test", $exporter->format_data( $data ) ); + + $time = time(); + $data = new WC_DateTime( "@{$time}", new DateTimeZone( 'UTC' ) ); + $this->assertEquals( date( 'Y-m-d G:i:s', $time ), $exporter->format_data( $data ) ); + + $data = true; + $this->assertEquals( 1, $exporter->format_data( $data ) ); + + $data = false; + $this->assertEquals( 0, $exporter->format_data( $data ) ); + } + + /** + * Test escape_data to prevent regressions that could open security holes. + * @since 3.1.0 + */ + public function test_format_term_ids() { + $exporter = new WC_Product_CSV_Exporter(); + + $term1 = wp_insert_category( array( 'cat_name' => 'cat1' ) ); + $term2 = wp_insert_category( array( 'cat_name' => 'cat2' ) ); + $term3 = wp_insert_category( array( 'cat_name' => 'cat3' ) ); + + $expected = "cat1, cat2, cat3"; + $this->assertEquals( $expected, $exporter->format_term_ids( array( $term1, $term2, $term3 ), 'category' ) ); + + wp_insert_category( array( 'cat_ID' => $term2, 'cat_name' => 'cat2', 'category_parent' => $term1 ) ); + + $expected = "cat1, cat1 > cat2, cat3"; + $this->assertEquals( $expected, $exporter->format_term_ids( array( $term1, $term2, $term3 ), 'category' ) ); + + wp_insert_category( array( 'cat_ID' => $term3, 'cat_name' => 'cat3', 'category_parent' => $term2 ) ); + $expected = "cat1, cat1 > cat2, cat1 > cat2 > cat3"; + $this->assertEquals( $expected, $exporter->format_term_ids( array( $term1, $term2, $term3 ), 'category' ) ); + } + + /** + * Test prepare_data_to_export. + * @since 3.1.0 + */ + public function test_prepare_data_to_export() { + add_filter( 'woocommerce_product_export_row_data', array( $this, 'verify_exported_data' ), 10, 2 ); + $exporter = new WC_Product_CSV_Exporter(); + + $product = WC_Helper_Product::create_simple_product(); + $product->set_description( 'Test description' ); + $product->set_short_description( 'Test short description' ); + $product->set_weight( 12.5 ); + $product->set_height( 10 ); + $product->set_length( 20 ); + $product->set_width( 1 ); + + $sale_start = time(); + $sale_end = $sale_start + DAY_IN_SECONDS; + $product->set_date_on_sale_from( $sale_start ); + $product->set_date_on_sale_to( $sale_end ); + + $product->save(); + WC_Helper_Product::create_external_product(); + WC_Helper_Product::create_grouped_product(); + WC_Helper_Product::create_variation_product(); + + $exporter->prepare_data_to_export(); + } + + /** + * Verify one product for test_perpare_data_to_export. + * @since 3.1.0 + */ + public function verify_exported_data( $row, $product ) { + $this->assertEquals( $product->get_id(), $row['id'] ); + $this->assertEquals( $product->get_type(), $row['type'] ); + $this->assertEquals( $product->get_sku(), $row['sku'] ); + $this->assertEquals( $product->get_name(), $row['name'] ); + $this->assertEquals( $product->get_short_description(), $row['short_description'] ); + $this->assertEquals( $product->get_description(), $row['description'] ); + $this->assertEquals( $product->get_tax_status(), $row['tax_status'] ); + $this->assertEquals( $product->get_width(), $row['width'] ); + $this->assertEquals( $product->get_height(), $row['height'] ); + $this->assertEquals( $product->get_length(), $row['length'] ); + $this->assertEquals( $product->get_weight(), $row['weight'] ); + $this->assertEquals( $product->get_featured(), $row['featured'] ); + $this->assertEquals( $product->get_sold_individually(), $row['sold_individually'] ); + $this->assertEquals( $product->get_date_on_sale_from(), $row['date_on_sale_from'] ); + $this->assertEquals( $product->get_date_on_sale_to(), $row['date_on_sale_to'] ); + $this->assertEquals( 'publish' === $product->get_status(), $row['published'] ); + $this->assertEquals( 'instock' === $product->get_stock_status(), $row['stock_status'] ); + + $this->assertContains( $row['catalog_visibility'], array( 'visible', 'catalog', 'search', 'hidden' ) ); + $this->assertContains( $row['backorders'], array( 1, 0, 'notify' ) ); + + $expected_parent = ''; + $parent_id = $product->get_parent_id(); + if ( $parent_id ) { + $parent = wc_get_product( $parent_id ); + $expected_parent = $parent->get_sku() ? $parent->get_sku() : 'id:' . $parent->get_id(); + } + $this->assertEquals( $expected_parent, $row['parent_id'] ); + + return $row; + } +}