diff --git a/uc_attribute/uc_attribute.admin.inc b/uc_attribute/uc_attribute.admin.inc index 7ac73e5..015b13c 100644 --- a/uc_attribute/uc_attribute.admin.inc +++ b/uc_attribute/uc_attribute.admin.inc @@ -191,6 +191,120 @@ function uc_attribute_delete_confirm_submit($form, &$form_state) { } /** + * Form builder for bulk product updates. + * + * @see uc_attribute_bulk_update_flush() + * @see uc_attribute_bulk_update_flush_all() + * @see theme_uc_attribute_bulk_update_form() + * @ingroup forms + */ +function uc_attribute_bulk_update_form($form_state, $class) { + drupal_set_title(check_plain($class->name)); + + $form['node_type'] = array( + '#type' => 'value', + '#value' => $class->pcid, + ); + + // Select all products with current class. + $result = pager_query("SELECT n.nid, n.title, n.status, nt.name FROM {node} n LEFT JOIN {node_type} nt ON nt.type = n.type WHERE nt.type = '%s'", 50, 0, NULL, $class->pcid); + $nodes = array(); + while ($node = db_fetch_object($result)) { + $nodes[$node->nid] = ''; + $form['title'][$node->nid] = array('#value' => l($node->title, 'node/' . $node->nid)); + $form['status'][$node->nid] = array('#value' => ($node->status ? t('published') : t('not published'))); + $form['type'][$node->nid] = array('#value' => check_plain($node->name)); + } + + $form['nodes'] = array('#type' => 'checkboxes', '#options' => $nodes); + + $form['flush'] = array( + '#type' => 'submit', + '#value' => t('Flush selected'), + '#submit' => array('uc_attribute_bulk_update_flush'), + ); + $form['flush_all'] = array( + '#type' => 'submit', + '#value' => t('Flush all'), + '#submit' => array('uc_attribute_bulk_update_flush_all'), + ); + + $form['pager'] = array('#value' => theme('pager', NULL, 50, 0)); + + return $form; +} + +/** + * Displays the bulk product update form. + * + * @see uc_attribute_bulk_update_form() + * @ingroup themeable + */ +function theme_uc_attribute_bulk_update_form($form) { + $output = ''; + + $has_products = isset($form['title']) && is_array($form['title']); + + $header[] = theme('table_select_header_cell'); + $header[] = t('Title'); + $header[] = t('Status'); + $header[] = t('Type'); + + if ($has_products) { + foreach (element_children($form['title']) as $key) { + $row = array(); + $row[] = drupal_render($form['nodes'][$key]); + $row[] = drupal_render($form['title'][$key]); + $row[] = drupal_render($form['status'][$key]); + $row[] = drupal_render($form['type'][$key]); + $rows[] = $row; + } + } + else { + $rows[] = array(array('data' => t('No products available.'), 'colspan' => '6')); + } + + $output .= theme('table', $header, $rows); + if ($form['pager']['#value']) { + $output .= drupal_render($form['pager']); + } + + $output .= drupal_render($form); + + $output .= '
' . t('Flush actions take place immediately and cannot be undone.') . '
'; + + return $output; +} + +/** + * Form submission handler for uc_attribute_bulk_update_form(). + * + * @see uc_attribute_bulk_update_form() + */ +function uc_attribute_bulk_update_flush($form, &$form_state) { + $nodes = array_filter($form_state['values']['nodes']); + foreach ($nodes as $nid) { + $node = node_load($nid); + uc_attribute_node_reset($node); + } + drupal_set_message(t("Bulk update completed successfully on selected products.")); +} + +/** + * Form submission handler for uc_attribute_bulk_update_form(). + * + * @see uc_attribute_bulk_update_form() + */ +function uc_attribute_bulk_update_flush_all($form, &$form_state) { + $result = db_query("SELECT nid FROM {node} WHERE type = '%s'", $form_state['values']['node_type']); + while ($item = db_fetch_object($result)) { + $node = node_load($item->nid); + uc_attribute_node_reset($node); + } + drupal_set_message(t("Bulk update completed successfully on all products in this class.")); +} + +/** * Changes the display of attribute option prices. * * @ingroup forms @@ -419,7 +533,15 @@ function uc_attribute_option_form($form_state, $attribute, $option = NULL) { '#default_value' => $option->weight, '#weight' => 3, ); - + if (isset($form['oid'])) { + $form['bulk_update'] = array( + '#type' => 'checkbox', + '#title' => t('Update existing products?'), + '#description' => t('If selected, existing products and product classes with this attribute will be updated with the values on this page.Warning: any product level customizations will be lost!
To perform bulk updates on specific option values without resetting attributes to the class defaults, go to Store administration->Attributes and edit the option, selecting the checkbox to update existing products.', array('!url' => url('admin/store/attributes'))); } } @@ -155,6 +159,15 @@ function uc_attribute_menu() { 'weight' => 2, 'file' => 'uc_attribute.admin.inc', ); + $items['admin/store/products/classes/%uc_product_class/attributes_bulk'] = array( + 'title' => 'Bulk', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('uc_attribute_bulk_update_form', 4, 'class'), + 'access callback' => 'uc_attribute_product_class_access', + 'type' => MENU_LOCAL_TASK, + 'weight' => 3, + 'file' => 'uc_attribute.admin.inc', + ); // Insert subitems into the edit node page for product types. $items['node/%node/edit/attributes'] = array( @@ -177,6 +190,15 @@ function uc_attribute_menu() { 'weight' => 1, 'file' => 'uc_attribute.admin.inc', ); + $items['node/%node/edit/attributes/reset'] = array( + 'title' => 'Reset to defaults', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('uc_attribute_node_reset_confirm', 1), + 'access callback' => 'uc_attribute_product_access', + 'access arguments' => array(1), + 'type' => MENU_CALLBACK, + 'file' => 'uc_attribute.admin.inc', + ); $items['node/%node/edit/options'] = array( 'title' => 'Options', 'page callback' => 'drupal_get_form', @@ -271,6 +293,10 @@ function uc_attribute_theme() { 'arguments' => array('product' => NULL), 'file' => 'uc_attribute.admin.inc', ), + 'uc_attribute_bulk_update_form' => array( + 'arguments' => array('form' => NULL), + 'file' => 'uc_attribute.admin.inc', + ), ); } @@ -349,23 +375,10 @@ function uc_attribute_nodeapi(&$node, $op, $arg3 = NULL, $arg4 = NULL) { } break; case 'insert': - switch ($GLOBALS['db_type']) { - case 'mysqli': - case 'mysql': - db_query("INSERT IGNORE INTO {uc_product_attributes} (nid, aid, label, ordering, required, display, default_option) SELECT %d, aid, label, ordering, required, display, default_option FROM {uc_class_attributes} WHERE pcid = '%s'", $node->nid, $node->type); - db_query("INSERT IGNORE INTO {uc_product_options} (nid, oid, cost, price, weight, ordering) SELECT %d, oid, cost, price, weight, ordering FROM {uc_class_attribute_options} WHERE pcid = '%s'", $node->nid, $node->type); - break; - case 'pgsql': - db_query("INSERT INTO {uc_product_attributes} (nid, aid, label, ordering, required, display, default_option) SELECT %d, aid, label, ordering, required, display, default_option FROM {uc_class_attributes} WHERE pcid = '%s'", $node->nid, $node->type); - db_query("INSERT INTO {uc_product_options} (nid, oid, cost, price, weight, ordering) SELECT %d, oid, cost, price, weight, ordering FROM {uc_class_attribute_options} WHERE pcid = '%s'", $node->nid, $node->type); - break; - } - + uc_attribute_node_insert($node); break; case 'delete': - db_query("DELETE FROM {uc_product_options} WHERE nid = %d", $node->nid); - db_query("DELETE FROM {uc_product_adjustments} WHERE nid = %d", $node->nid); - db_query("DELETE FROM {uc_product_attributes} WHERE nid = %d", $node->nid); + uc_attribute_node_delete($node); break; case 'update index': $output = ''; @@ -386,6 +399,40 @@ function uc_attribute_nodeapi(&$node, $op, $arg3 = NULL, $arg4 = NULL) { } } +/** + * Add default attributes to a product node. + */ +function uc_attribute_node_insert($node) { + switch ($GLOBALS['db_type']) { + case 'mysqli': + case 'mysql': + db_query("INSERT IGNORE INTO {uc_product_attributes} (nid, aid, label, ordering, required, display, default_option) SELECT %d, aid, label, ordering, required, display, default_option FROM {uc_class_attributes} WHERE pcid = '%s'", $node->nid, $node->type); + db_query("INSERT IGNORE INTO {uc_product_options} (nid, oid, cost, price, weight, ordering) SELECT %d, oid, cost, price, weight, ordering FROM {uc_class_attribute_options} WHERE pcid = '%s'", $node->nid, $node->type); + break; + case 'pgsql': + db_query("INSERT INTO {uc_product_attributes} (nid, aid, label, ordering, required, display, default_option) SELECT %d, aid, label, ordering, required, display, default_option FROM {uc_class_attributes} WHERE pcid = '%s'", $node->nid, $node->type); + db_query("INSERT INTO {uc_product_options} (nid, oid, cost, price, weight, ordering) SELECT %d, oid, cost, price, weight, ordering FROM {uc_class_attribute_options} WHERE pcid = '%s'", $node->nid, $node->type); + break; + } +} + +/** + * Remove all attributes from a product node. + */ +function uc_attribute_node_delete($node) { + db_query("DELETE FROM {uc_product_options} WHERE nid = %d", $node->nid); + db_query("DELETE FROM {uc_product_adjustments} WHERE nid = %d", $node->nid); + db_query("DELETE FROM {uc_product_attributes} WHERE nid = %d", $node->nid); +} + +/** + * Reset attributes to class defaults for a product node. + */ +function uc_attribute_node_reset($node) { + uc_attribute_node_delete($node); + uc_attribute_node_insert($node); +} + /****************************************************************************** * Ubercart Hooks * ******************************************************************************/ diff --git a/uc_attribute/uc_attribute.test b/uc_attribute/uc_attribute.test index ad705b0..975c180 100644 --- a/uc_attribute/uc_attribute.test +++ b/uc_attribute/uc_attribute.test @@ -25,7 +25,7 @@ class UbercartAttributeTestCase extends UbercartTestHelper { * Overrides DrupalWebTestCase::setUp(). */ function setUp() { - parent::setUp(array('uc_attribute'), array('administer attributes')); + parent::setUp(array('uc_attribute'), array('administer attributes', 'administer nodes')); $this->drupalLogin($this->adminUser); } @@ -538,6 +538,57 @@ class UbercartAttributeTestCase extends UbercartTestHelper { } /** + * Tests the "bulk edit attribute options" user interface. + */ + public function testAttributeUIAttributeOptionsBulkEdit() { + $attribute = self::createAttribute(); + $option = self::createAttributeOption(array('aid' => $attribute->aid)); + uc_attribute_option_save($option); + + $class = $this->createProductClass(); + uc_attribute_subject_save($attribute, 'class', $class->pcid); + uc_attribute_subject_option_save($option, 'class', $class->pcid); + + $product = $this->createProduct(); + uc_attribute_subject_save($attribute, 'product', $product->nid); + uc_attribute_subject_option_save($option, 'product', $product->nid); + + $edit = (array) self::createAttributeOption(array('aid' => $attribute->aid), FALSE); + unset($edit['aid']); + $edit['bulk_update'] = TRUE; + $this->drupalPost('admin/store/attributes/'. $attribute->aid .'/options/'. $option->oid .'/edit', $edit, t('Submit')); + unset($edit['bulk_update']); + + $this->assertText('Bulk updates applied', t('Bulk update selected.'), t('Ubercart')); + + $option = uc_attribute_subject_option_load($option->oid, 'class', $class->pcid); + $fields_ok = TRUE; + foreach ($edit as $field => $value) { + if ($option->$field != $value) { + $this->showVar($option); + $this->showVar($edit); + $fields_ok = FALSE; + break; + } + } + + $this->assertTrue($fields_ok, t('Class bulk updated successfully.'), t('Ubercart')); + + $product_attributes = uc_attribute_load_multiple(array(), 'product', $product->nid); + $fields_ok = TRUE; + foreach ($edit as $field => $value) { + if ($product_attributes[$attribute->aid]->options[$option->oid]->$field != $value) { + $this->showVar($product_attributes); + $this->showVar($edit); + $fields_ok = FALSE; + break; + } + } + + $this->assertTrue($fields_ok, t('Product bulk updated successfully.'), t('Ubercart')); + } + + /** * Tests the "delete attribute option" user interface. */ public function testAttributeUIAttributeOptionsDelete() { @@ -663,6 +714,58 @@ class UbercartAttributeTestCase extends UbercartTestHelper { } /** + * Tests the product node attribute user interface. + */ + public function testAttributeUIProductAttributeOverview() { + $class = $this->createProductClass(); + $attribute = self::createAttribute(); + uc_attribute_subject_save($attribute, 'class', $class->pcid); + $product = $this->createProduct(array('type' => $class->pcid)); + + // Check product has attribute added by default. + $loaded_attribute = uc_attribute_load($attribute->aid, $product->nid, 'product'); + $this->assertEqual($attribute->name, $loaded_attribute->name, t('Product attribute added by default.'), t('Ubercart')); + uc_attribute_subject_delete($attribute->aid, 'product', $product->nid); + + $this->drupalGet('node/'. $product->nid .'/edit/attributes/add'); + $edit = array(); + $edit['add_attributes[]'] = $attribute->aid; + $this->drupalPost(NULL, $edit, t('Add attributes')); + $this->assertText($attribute->name, t('Product attribute added.'), t('Ubercart')); + + $edit = array(); + $edit["attributes[{$attribute->aid}][remove]"] = TRUE; + $this->drupalPost(NULL, $edit, t('Save changes')); + $this->assertText(t('You must first add attributes to this product.'), t('Product attribute removed.'), t('Ubercart')); + + $this->drupalPost(NULL, array(), t('Reset to defaults')); + $this->drupalPost(NULL, array(), t('Reset')); + $this->assertText($attribute->name, t('Product attribute reset successfully.'), t('Ubercart')); + } + + /** + * Tests the bulk product attribute update user interface. + */ + public function testAttributeUIProductAttributeBulkUpdate() { + $class = $this->createProductClass(); + $attribute = self::createAttribute(); + uc_attribute_subject_save($attribute, 'class', $class->pcid); + $product = $this->createProduct(array('type' => $class->pcid)); + + $edit = array(); + $edit["attributes[{$attribute->aid}][remove]"] = TRUE; + $this->drupalPost('node/'. $product->nid .'/edit/attributes', $edit, t('Save changes')); + $this->assertText(t('You must first add attributes to this product.'), t('Product attribute removed.'), t('Ubercart')); + + $this->drupalGet('admin/store/products/classes/'. $class->pcid .'/attributes_bulk'); + $this->assertText($product->title, t('Product name found.'), t('Ubercart')); + $this->drupalPost(NULL, array(), t('Flush all')); + + $this->drupalGet('node/'. $product->nid .'/edit/attributes'); + $this->assertText($attribute->name, t('Product attribute reset successfully.'), t('Ubercart')); + } + + /** * Creates a product adjustment SKU. * * @param $data diff --git a/uc_store/uc_store.test b/uc_store/uc_store.test index a567b97..cd3c385 100644 --- a/uc_store/uc_store.test +++ b/uc_store/uc_store.test @@ -95,6 +95,8 @@ class UbercartTestHelper extends DrupalWebTestCase { $product_class = (object) $product_class; drupal_write_record('uc_product_classes', $product_class); + uc_product_node_info(TRUE); + node_types_rebuild(); return $product_class; }