diff --git a/.travis-before-script.sh b/.travis-before-script.sh index 8cce97d..a92e8ae 100644 --- a/.travis-before-script.sh +++ b/.travis-before-script.sh @@ -16,8 +16,8 @@ cd "$DRUPAL_TI_DRUPAL_DIR" mkdir -p "$DRUPAL_TI_DRUPAL_DIR/$DRUPAL_TI_MODULES_PATH" drush dl -y commerce -drush dl -y addressfield rules ctools entity views -drush en -y commerce_cart commerce_customer_ui commerce_product_ui commerce_line_item_ui commerce_order_ui commerce_payment commerce_payment_example commerce_tax_ui simpletest +drush dl -y addressfield rules ctools entity views physical +drush en -y commerce_cart commerce_customer_ui commerce_product_ui commerce_line_item_ui commerce_order_ui commerce_payment commerce_payment_example commerce_tax_ui simpletest physical drush en -y commerce_xls_import git clone https://github.com/box/spout.git mv spout sites/all/libraries/spout diff --git a/classes/CommerceXlsImportDimensionsHandler.inc b/classes/CommerceXlsImportDimensionsHandler.inc new file mode 100644 index 0000000..15bb307 --- /dev/null +++ b/classes/CommerceXlsImportDimensionsHandler.inc @@ -0,0 +1,116 @@ + $unit)); + } + elseif (!is_numeric($dimensions[$unit])) { + $errors[] = t('The "!unit" value must be numeric.', array('!unit' => $unit)); + } + } + + if (!isset($dimensions['unit'])) { + $errors[] = t('The "unit" value is not found.'); + } + elseif (!in_array($dimensions['unit'], $allowed_units)) { + $allowed_units_formatted = implode(', ', $allowed_units); + $errors[] = t('The "unit" value must be one of the following: !allowed_units_formatted.', array('!allowed_units_formatted' => $allowed_units_formatted)); + } + + $valid = empty($errors); + return array( + 'status' => $valid ? COMMERCE_XLS_IMPORT_DATA_SUCCESS : COMMERCE_XLS_IMPORT_DATA_ERROR, + 'message' => $valid ? NULL : implode(' ', $errors), + ); + } + + /** + * {@inheritdoc} + */ + public static function set($value, EntityDrupalWrapper $wrapper, $field_name) { + $valid = self::validate($value, $wrapper, $field_name); + + if ($valid['status'] === COMMERCE_XLS_IMPORT_DATA_SUCCESS && !empty($value)) { + $dimensions = self::fromCsv($value); + module_load_include('module', 'physical', 'physical'); + + $field_value = physical_dimensions_field_data_auto_creation(); + $field_value['length'] = $dimensions['length']; + $field_value['width'] = $dimensions['width']; + $field_value['height'] = $dimensions['height']; + $field_value['unit'] = $dimensions['unit']; + + $wrapper->{$field_name} = $field_value; + } + + return $valid; + } + + /** + * {@inheritdoc} + */ + public static function get(EntityDrupalWrapper $wrapper, $field_name) { + $dimensions = $wrapper->{$field_name}->value(); + return self::toCsv($dimensions); + } + + /** + * {@inheritdoc} + */ + public static function toCsv($value, $delimiter = ',', $enclosure = '"') { + $formatted = array(); + if (is_array($value)) { + foreach ($value as $k => $v) { + // $k would be length/width/height/unit. + // $v would be a numerical value or in/ft etc. + $k = strtolower(trim($k)); + $v = strtolower(trim($v)); + $formatted[] = ($k . '=' . $v); + } + } + return parent::toCsv($formatted); + } + + /** + * {@inheritdoc} + */ + public static function fromCsv($value) { + $dimensions = parent::fromCsv($value); + $unserialized = array(); + + if (is_array($dimensions)) { + foreach ($dimensions as $dimension) { + $dimension = trim($dimension); + $split = explode('=', $dimension); + if (count($split) == 2) { + // $k would be length/width/height/unit. + // $v would be a numerical value or in/ft etc. + $k = strtolower(trim($split[0])); + $v = strtolower(trim($split[1])); + $unserialized[$k] = $v; + } + } + } + + return $unserialized; + } + +} diff --git a/classes/CommerceXlsImportWeightHandler.inc b/classes/CommerceXlsImportWeightHandler.inc new file mode 100644 index 0000000..718386d --- /dev/null +++ b/classes/CommerceXlsImportWeightHandler.inc @@ -0,0 +1,112 @@ + $allowed_units_formatted)); + } + + $valid = empty($errors); + return array( + 'status' => $valid ? COMMERCE_XLS_IMPORT_DATA_SUCCESS : COMMERCE_XLS_IMPORT_DATA_ERROR, + 'message' => $valid ? NULL : implode(' ', $errors), + ); + } + + /** + * {@inheritdoc} + */ + public static function set($value, EntityDrupalWrapper $wrapper, $field_name) { + $valid = self::validate($value, $wrapper, $field_name); + + if ($valid['status'] === COMMERCE_XLS_IMPORT_DATA_SUCCESS && !empty($value)) { + $weight = self::fromCsv($value); + module_load_include('module', 'physical', 'physical'); + + $field_value = physical_weight_field_data_auto_creation(); + $field_value['weight'] = $weight['weight']; + $field_value['unit'] = $weight['unit']; + + $wrapper->{$field_name} = $field_value; + } + + return $valid; + } + + /** + * {@inheritdoc} + */ + public static function get(EntityDrupalWrapper $wrapper, $field_name) { + $weight = $wrapper->{$field_name}->value(); + return self::toCsv($weight); + } + + /** + * {@inheritdoc} + */ + public static function toCsv($value, $delimiter = ',', $enclosure = '"') { + $formatted = array(); + if (is_array($value)) { + foreach ($value as $k => $v) { + // $k would be weight/unit. + // $v would be a numerical value or lb/kg etc. + $k = strtolower(trim($k)); + $v = strtolower(trim($v)); + $formatted[] = ($k . '=' . $v); + } + } + return parent::toCsv($formatted); + } + + /** + * {@inheritdoc} + */ + public static function fromCsv($value) { + $dimensions = parent::fromCsv($value); + $unserialized = array(); + + if (is_array($dimensions)) { + foreach ($dimensions as $dimension) { + $dimension = trim($dimension); + $split = explode('=', $dimension); + if (count($split) == 2) { + // $k would be weight/unit. + // $v would be a numerical value or lb/kg etc. + $k = strtolower(trim($split[0])); + $v = strtolower(trim($split[1])); + $unserialized[$k] = $v; + } + } + } + + return $unserialized; + } + +} diff --git a/commerce_xls_import.info b/commerce_xls_import.info index 75e4e68..55f10f8 100644 --- a/commerce_xls_import.info +++ b/commerce_xls_import.info @@ -11,6 +11,8 @@ dependencies[] = libraries stylesheets[all][] = css/commerce_xls_import.css files[] = tests/commerce_xls_import.test +files[] = tests/commerce_xls_import_physical.test +files[] = tests/commerce_xls_import_physical.unit.test files[] = classes/CommerceXlsImportSettings.inc files[] = classes/CommerceXlsImportReadFilter.inc @@ -30,3 +32,5 @@ files[] = classes/CommerceXlsImportTaxonomyTermReferenceHandler.inc files[] = classes/CommerceXlsImportTextHandler.inc files[] = classes/CommerceXlsImportTextWithSummaryHandler.inc files[] = classes/CommerceXlsImportVariationTitleHandler.inc +files[] = classes/CommerceXlsImportDimensionsHandler.inc +files[] = classes/CommerceXlsImportWeightHandler.inc diff --git a/commerce_xls_import.module b/commerce_xls_import.module index e2beff2..c16a6c2 100644 --- a/commerce_xls_import.module +++ b/commerce_xls_import.module @@ -202,6 +202,14 @@ function commerce_xls_import_commerce_xls_import_field_type_handler_info() { 'class_name' => 'CommerceXlsImportVariationTitleHandler', ); + $handlers['physical_dimensions'] = array( + 'class_name' => 'CommerceXlsImportDimensionsHandler', + ); + + $handlers['physical_weight'] = array( + 'class_name' => 'CommerceXlsImportWeightHandler', + ); + return $handlers; } diff --git a/tests/commerce_xls_import_physical.test b/tests/commerce_xls_import_physical.test new file mode 100644 index 0000000..ceef141 --- /dev/null +++ b/tests/commerce_xls_import_physical.test @@ -0,0 +1,336 @@ + 'Commerce XLS Import (commerce_physical) Tests', + 'description' => 'Ensure Commerce XLS Import functions properly with the commerce_physical module', + 'group' => 'Drupal Commerce XLS Import', + ); + } + + /** + * Creates a physical field on the commerce_product 'product' bundle. + * + * @param string $field_type + * The field type, e.g. 'physical_weight'. + */ + private function createProductPhysicalField($field_type) { + if ($field_type == 'physical_dimensions') { + $field_name = 'field_dimensions'; + $widget_type = 'physical_dimensions_textfields'; + $display_type = 'physical_dimensions_formatted'; + } + elseif ($field_type == 'physical_weight') { + $field_name = 'field_weight'; + $widget_type = 'physical_weight_textfield'; + $display_type = 'physical_weight_formatted'; + } + else { + return; + } + + $field_info = field_info_field($field_name); + if (empty($field_info)) { + $field = array( + 'field_name' => $field_name, + 'type' => $field_type, + 'translatable' => FALSE, + ); + field_create_field($field); + } + + $instance = field_info_instance('commerce_product', $field_name, 'product'); + if (empty($instance)) { + $instance = array( + 'field_name' => $field_name, + 'entity_type' => 'commerce_product', + 'label' => 'Product', + 'bundle' => 'product', + 'description' => 'Test field.', + 'required' => TRUE, + + 'widget' => array( + 'type' => $widget_type, + ), + + 'display' => array( + 'default' => array( + 'label' => 'hidden', + 'type' => $display_type, + ), + 'line_item' => array( + 'label' => 'hidden', + 'type' => 'hidden', + ), + 'node_teaser' => array( + 'label' => 'hidden', + 'type' => 'hidden', + ), + ), + ); + + field_create_instance($instance); + } + } + + /** + * Setup(). + * + * @{inheritdoc} + */ + public function setUp() { + libraries_load('spout'); + + $modules = array('commerce_xls_import', 'physical'); + $modules = parent::setUpHelper('all', $modules); + parent::setUp($modules); + + // Create a dummy product display content type. + $this->createDummyProductDisplayContentType('product_display', TRUE, 'field_product', -1); + $this->createProductPhysicalField('physical_weight'); + $this->createProductPhysicalField('physical_dimensions'); + + $this->privilegedUser = $this->drupalCreateUser(array( + 'administer commerce import', + 'administer commerce_product entities', + )); + + $this->assertTrue(module_exists('physical'), 'physical module is enabled'); + + $this->drupalLogin($this->privilegedUser); + } + + /** + * Creates an import sheet with a pre-made header row. + * + * @param string $path + * The path for the sheet to be created. + * + * @return \Box\Spout\Writer\WriterInterface + * The import writer. + */ + private function createImportSheet($path) { + $import_writer = WriterFactory::create(Type::XLSX); + $import_writer->openToFile($path); + + // Make the rows for the import sheet. + $header_row = array( + 'title', + 'variation_title', + 'sku', + 'body', + 'commerce_price', + 'status', + 'language', + 'field_weight', + 'field_dimensions', + ); + + $import_writer->addRow($header_row); + + return $import_writer; + } + + /** + * Creates a product row for an import sheet. + * + * @param string $title + * The product display title. + * @param string $variation_title + * The variation title. + * @param string $sku + * The product SKU. + * @param array $weight + * An array of weight details. + * @param array $dimensions + * An array of dimension details. + * + * @return array + * The array of details for the product row. + */ + private function createProductRow($title, $variation_title, $sku, $weight, $dimensions) { + return array( + $title, + $variation_title, + $sku, + 'This is a test product', + '15.99', + '1', + 'en', + 'weight=' . $weight['weight'] . ',unit=' . $weight['unit'], + 'length=' . $dimensions['length'] . ',width=' . $dimensions['width'] . ',height=' . $dimensions['height'] . ',unit=' . $dimensions['unit'], + ); + } + + /** + * Test that weight and dimensions import correctly on new product. + */ + public function testCommerceXlsImportPhysicalWeightDimensionsNew() { + $import_sheet_path = drupal_realpath(file_build_uri('test_import.xlsx')); + + $import_sheet = $this->createImportSheet($import_sheet_path); + + $weight = array( + 'weight' => '15', + 'unit' => 'lb', + ); + $dimensions = array( + 'length' => '15', + 'width' => '20', + 'height' => '25', + 'unit' => 'in', + ); + $product_row = $this->createProductRow('Weight', 'Weight (Medium)', 'weight-md', $weight, $dimensions); + + $import_sheet->addRow($product_row); + $import_sheet->close(); + + $edit = array( + 'display_type' => 'product_display', + 'files[import_file]' => $import_sheet_path, + ); + $this->drupalPost('admin/commerce/products/commerce_xls_import', $edit, 'Begin Full Import'); + + // Assert the import completed with no errors. + $this->assertText('Import Completed, 1 records processed with 0 errors.', 'Import file processed'); + + // Grab the product. + $query = new EntityFieldQuery(); + $results = $query->entityCondition('entity_type', 'commerce_product') + ->entityCondition('bundle', 'product') + ->propertyCondition('sku', 'weight-md') + ->execute(); + $product_ids = array_keys($results['commerce_product']); + $product_id = reset($product_ids); + $product_wrapper = entity_metadata_wrapper('commerce_product', $product_id); + + // Assert the product has proper weight. + $product_weight = $product_wrapper->field_weight->value(); + $this->assertEqual($product_weight['weight'], $weight['weight'], 'Weight imported to newly created product'); + $this->assertEqual($product_weight['unit'], $weight['unit'], 'Weight unit imported to newly created product'); + + // Assert the product has proper dimensions. + $product_dimensions = $product_wrapper->field_dimensions->value(); + $this->assertEqual($product_dimensions['length'], $dimensions['length'], 'Length imported to newly created product'); + $this->assertEqual($product_dimensions['width'], $dimensions['width'], 'Width imported to newly created product'); + $this->assertEqual($product_dimensions['height'], $dimensions['height'], 'Height imported to newly created product'); + $this->assertEqual($product_dimensions['unit'], $dimensions['unit'], 'Dimensions unit imported to newly created product'); + } + + /** + * Test that weight and dimensions import correctly on existing product. + */ + public function testCommerceXlsImportPhysicalWeightDimensionsExisting() { + // Verify the product doesn't exist. + $query = new EntityFieldQuery(); + $results = $query->entityCondition('entity_type', 'commerce_product') + ->entityCondition('bundle', 'product') + ->propertyCondition('sku', 'weight-md') + ->execute(); + $this->assertEqual(count($results), 0, 'The product does not exist'); + + // Create a product. + $weight = array( + 'weight' => '15', + 'unit' => 'lb', + ); + $dimensions = array( + 'length' => '15', + 'width' => '20', + 'height' => '25', + 'unit' => 'in', + ); + $product = commerce_product_new('product'); + $product_wrapper = entity_metadata_wrapper('commerce_product', $product); + $product_wrapper->sku->set('weight-md'); + $product_wrapper->title->set('Weight (Medium)'); + $product_wrapper->field_weight->set($weight); + $product_wrapper->field_dimensions->set($dimensions); + $product_wrapper->save(); + + // Verify the product now exists. + $query = new EntityFieldQuery(); + $results = $query->entityCondition('entity_type', 'commerce_product') + ->entityCondition('bundle', 'product') + ->propertyCondition('sku', 'weight-md') + ->execute(); + $this->assertEqual(count($results), 1, 'The product was created'); + + // Create the import sheet. + $import_sheet_path = drupal_realpath(file_build_uri('test_import.xlsx')); + + $import_sheet = $this->createImportSheet($import_sheet_path); + + $new_weight = array( + 'weight' => '20', + 'unit' => 'oz', + ); + $new_dimensions = array( + 'length' => '10.23', + 'width' => '10.66', + 'height' => '33', + 'unit' => 'ft', + ); + $product_row = $this->createProductRow('Weight', 'Weight (Medium)', 'weight-md', $new_weight, $new_dimensions); + + $import_sheet->addRow($product_row); + $import_sheet->close(); + + $edit = array( + 'display_type' => 'product_display', + 'files[import_file]' => $import_sheet_path, + ); + $this->drupalPost('admin/commerce/products/commerce_xls_import', $edit, 'Begin Full Import'); + + // Assert the import completed with no errors. + $this->assertText('Import Completed, 1 records processed with 0 errors.', 'Import file processed'); + + // Reset entity cache to grab fresh version of product. + entity_get_controller('commerce_product')->resetCache(); + + // Grab the product. + $query = new EntityFieldQuery(); + $results = $query->entityCondition('entity_type', 'commerce_product') + ->entityCondition('bundle', 'product') + ->propertyCondition('sku', 'weight-md') + ->execute(); + $product_ids = array_keys($results['commerce_product']); + $product_id = reset($product_ids); + $product_wrapper = entity_metadata_wrapper('commerce_product', $product_id); + + // Assert the product has the changed weight. + $product_weight = $product_wrapper->field_weight->value(); + $this->assertEqual($product_weight['weight'], $new_weight['weight'], 'Weight imported on existing product'); + $this->assertEqual($product_weight['unit'], $new_weight['unit'], 'Weight unit imported on existing product'); + + // Assert the product has the changed dimensions. + $product_dimensions = $product_wrapper->field_dimensions->value(); + $this->assertEqual($product_dimensions['length'], $new_dimensions['length'], 'Length imported on existing product'); + $this->assertEqual($product_dimensions['width'], $new_dimensions['width'], 'Width imported on existing product'); + $this->assertEqual($product_dimensions['height'], $new_dimensions['height'], 'Height imported on existing product'); + $this->assertEqual($product_dimensions['unit'], $new_dimensions['unit'], 'Dimensions unit imported on existing product'); + } + +} diff --git a/tests/commerce_xls_import_physical.unit.test b/tests/commerce_xls_import_physical.unit.test new file mode 100644 index 0000000..8933c79 --- /dev/null +++ b/tests/commerce_xls_import_physical.unit.test @@ -0,0 +1,182 @@ + 'Commerce XLS Import (commerce_physical) Unit Tests', + 'description' => 'Ensure Commerce XLS Import functions properly with the commerce_physical module', + 'group' => 'Drupal Commerce XLS Import', + ); + } + + /** + * Setup(). + * + * @{inheritdoc} + */ + public function setup() { + parent::setUp(); + module_load_include('inc', 'commerce_xls_import', 'classes/CommerceXlsImportValueHandlerInterface'); + module_load_include('inc', 'commerce_xls_import', 'classes/CommerceXlsImportValueHandler'); + module_load_include('inc', 'commerce_xls_import', 'classes/CommerceXlsImportDimensionsHandler'); + module_load_include('inc', 'commerce_xls_import', 'classes/CommerceXlsImportWeightHandler'); + } + + /** + * Tests that the DimensionsHandler serializes data correctly for a CSV. + */ + public function testWeightHandlerToCsv() { + // Assert that proper dimensions work. + $weight = array( + 'weight' => '30.000', + 'unit' => 'lb', + ); + $serialized = \CommerceXlsImportWeightHandler::toCsv($weight); + $expected = 'weight=30.000,unit=lb'; + $this->assertEqual($serialized, $expected, 'ToCsv function result matches expected string'); + + // Assert that leading/trailing whitespace is trimmed. + $weight = array( + 'weight' => '10 ', + 'unit' => ' kg ', + ); + $serialized = \CommerceXlsImportWeightHandler::toCsv($weight); + $expected = 'weight=10,unit=kg'; + $this->assertEqual($serialized, $expected, 'ToCsv function trims whitespace'); + + // Assert that capitalization is turned to lowercase. + $weight = array( + 'weIGht' => '10', + 'uNiT' => 'kG', + ); + $serialized = \CommerceXlsImportWeightHandler::toCsv($weight); + $expected = 'weight=10,unit=kg'; + $this->assertEqual($serialized, $expected, 'ToCsv function transforms text to lowercase'); + } + + /** + * Tests that the DimensionsHandler parses the data correctly from a CSV. + */ + public function testWeightHandlerFromCsv() { + // Assert that proper dimensions work. + $weight = 'weight=30.000,unit=lb'; + $parsed = \CommerceXlsImportWeightHandler::fromCsv($weight); + $expected = array( + 'weight' => '30.000', + 'unit' => 'lb', + ); + $this->assertEqual($parsed, $expected, 'FromCsv function result matches expected array'); + + // Assert that leading/trailing whitespace is trimmed. + $weight = ' weight = 30 , unit=lb '; + $parsed = \CommerceXlsImportWeightHandler::fromCsv($weight); + $expected = array( + 'weight' => '30', + 'unit' => 'lb', + ); + $this->assertEqual($parsed, $expected, 'FromCsv function trims whitespace'); + + // Assert that capitalization is turned to lowercase. + $weight = 'weIGht=30,UnIt=Lb'; + $parsed = \CommerceXlsImportWeightHandler::fromCsv($weight); + $expected = array( + 'weight' => '30', + 'unit' => 'lb', + ); + $this->assertEqual($parsed, $expected, 'FromCsv function transforms text to lowercase'); + } + + /** + * Tests that the DimensionsHandler serializes data correctly for a CSV. + */ + public function testDimensionsHandlerToCsv() { + // Assert that proper dimensions work. + $dimensions = array( + 'length' => '24.000', + 'width' => '10.000', + 'height' => '30.000', + 'unit' => 'in', + ); + $serialized = \CommerceXlsImportDimensionsHandler::toCsv($dimensions); + $expected = 'length=24.000,width=10.000,height=30.000,unit=in'; + $this->assertEqual($serialized, $expected, 'ToCsv function result matches expected string'); + + // Assert that leading/trailing whitespace is trimmed. + $dimensions = array( + 'length' => '20.00 ', + 'width' => ' 50.000', + 'height' => ' 10 ', + 'unit' => 'ft ', + ); + $serialized = \CommerceXlsImportDimensionsHandler::toCsv($dimensions); + $expected = 'length=20.00,width=50.000,height=10,unit=ft'; + $this->assertEqual($serialized, $expected, 'ToCsv function trims whitespace'); + + // Assert that capitalization is turned to lowercase. + $dimensions = array( + 'leNGth' => '20.00', + 'widtH' => '50.000', + 'Height' => '10', + 'uNIt' => 'fT', + ); + $serialized = \CommerceXlsImportDimensionsHandler::toCsv($dimensions); + $expected = 'length=20.00,width=50.000,height=10,unit=ft'; + $this->assertEqual($serialized, $expected, 'ToCsv function transforms text to lowercase'); + } + + /** + * Tests that the DimensionsHandler parses the data correctly from a CSV. + */ + public function testDimensionsHandlerFromCsv() { + // Assert that proper dimensions work. + $dimensions = 'length=24.000,width=10.000,height=30.000,unit=in'; + $parsed = \CommerceXlsImportDimensionsHandler::fromCsv($dimensions); + $expected = array( + 'length' => '24.000', + 'width' => '10.000', + 'height' => '30.000', + 'unit' => 'in', + ); + $this->assertEqual($parsed, $expected, 'FromCsv function result matches expected array'); + + // Assert that leading/trailing whitespace is trimmed. + $dimensions = ' length = 24 ,width= 10.0000 , height =30.0,unit=ft '; + $parsed = \CommerceXlsImportDimensionsHandler::fromCsv($dimensions); + $expected = array( + 'length' => '24', + 'width' => '10.0000', + 'height' => '30.0', + 'unit' => 'ft', + ); + $this->assertEqual($parsed, $expected, 'FromCsv function trims whitespace'); + + // Assert that capitalization is turned to lowercase. + $dimensions = 'leNGth=24,Width=10.0000,heighT=30.0,uNIt=FT'; + $parsed = \CommerceXlsImportDimensionsHandler::fromCsv($dimensions); + $expected = array( + 'length' => '24', + 'width' => '10.0000', + 'height' => '30.0', + 'unit' => 'ft', + ); + $this->assertEqual($parsed, $expected, 'FromCsv function transforms text to lowercase'); + } + +}