Index: updates.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/ecommerce/updates.inc,v retrieving revision 1.6.2.5 diff -u -F^f -r1.6.2.5 updates.inc --- updates.inc 30 Oct 2005 19:02:46 -0000 1.6.2.5 +++ updates.inc 13 Nov 2005 05:19:48 -0000 @@ -17,7 +17,8 @@ '2005-06-09' => 'update_7', '2005-06-25' => 'update_8', '2005-06-30' => 'update_9', - '2005-10-02' => 'update_10' + '2005-10-02' => 'update_10', + '2005-10-22' => 'update_11' ); function update_1() { @@ -144,6 +145,16 @@ function update_10() { return $ret; } +function update_11() { + if ($GLOBALS['db_type'] == 'mysql') { + $ret[] = update_sql('ALTER TABLE {ec_product} ADD parent INT(10) NOT NULL, ADD children VARCHAR(255) NOT NULL, ADD variation LONGTEXT NOT NULL'); + } + else { + // pgsql goes here. + } + return $ret; +} + function update_sql($sql) { $edit = $_POST["edit"]; $result = db_query($sql); Index: cart/cart.module =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/ecommerce/cart/cart.module,v retrieving revision 1.44.2.11 diff -u -F^f -r1.44.2.11 cart.module --- cart/cart.module 30 Oct 2005 19:02:46 -0000 1.44.2.11 +++ cart/cart.module 13 Nov 2005 05:19:49 -0000 @@ -140,7 +140,7 @@ function theme_cart_display_block() { foreach ($item as $i) { $node = node_load(array("nid" => $i->nid)); $total += product_adjust_price($node) * $i->qty; - $output .= l("$i->qty x $node->title", "node/$node->nid"). "
"; + $output .= l("$i->qty x $node->title", "node/" .($node->parent ? $node->parent : $node->nid)). "
"; } $output .= "
". payment_format($total) . "
"; $output .= '
'. t('Ready to checkout?', array('%checkout-url' => url('cart/checkout'))) .'
'; @@ -207,7 +207,7 @@ function cart_page() { switch (arg(1)) { case 'add': - cart_add_item(arg(2), arg(3)); + cart_add_item(arg(2), arg(3), 'redirect', $edit); $output = theme("cart_view"); break; @@ -581,8 +581,27 @@ function cart_get_id() { function cart_add_item($nid, $qty = 1, $action = "redirect", $data = array()) { /* Make sure we can add a product */ + $bool_cart_add = true; /* we assume we can until we can't */ $node = node_load(array('nid' => $nid)); - $bool_cart_add = module_invoke($node->ptype, 'productapi', $node, 'cart add item'); + + /* if this is a parent product, we do not add this directly, but the child + * instead. The parent is not really a product, just a place holder for + * all the related/sub products. */ + if ($node->children) { + if ($subproduct = module_invoke($node->ptype, 'productapi', $node, 'cart get subproduct', $data)) { + /* we have a sub product. We need to change the the parent to the + * child so the child is added to the cart, and not the parent */ + $node = $subproduct; + $nid = $node->nid; + } + else { + $bool_cart_add = false; + } + } + + /* if the parent has a subproduct then the $bool_cart_add may already be + * no, so why bother calling the hook */ + $bool_cart_add = $bool_cart_add ? module_invoke($node->ptype, 'productapi', $node, 'cart add item') : false; if (is_null($bool_cart_add)) { $bool_cart_add = true; } Index: product/product.module =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/ecommerce/product/product.module,v retrieving revision 1.58.2.7 diff -u -F^f -r1.58.2.7 product.module --- product/product.module 10 Nov 2005 14:43:23 -0000 1.58.2.7 +++ product/product.module 13 Nov 2005 05:19:55 -0000 @@ -41,7 +41,7 @@ function theme_product_view_collection() $columns = 3; $rows = 5; - $result = pager_query(db_rewrite_sql('SELECT n.nid FROM {node} n INNER JOIN {ec_product} p ON n.nid = p.nid WHERE n.status = 1 ORDER BY n.sticky DESC, n.created DESC'), $rows * $columns, 0); + $result = pager_query(db_rewrite_sql('SELECT n.nid FROM {node} n INNER JOIN {ec_product} p ON n.nid = p.nid WHERE n.status = 1 AND p.parent = 0 ORDER BY n.sticky DESC, n.created DESC'), $rows * $columns, 0); $output = ''; for ($i = 0; $node = db_fetch_object($result); $i++) { @@ -171,6 +171,12 @@ function product_link($type, $node = nul $links[] = t('sold out'); } } + if (user_access('create products') && !$node->parent && module_invoke($node->ptype, 'productapi', $node, 'subproduct_types')) { + $links[] = l(t('add sub product'), "node/add/product/parent/$node->nid", array(), drupal_get_destination()); + } + if ($node->parent) { + $links[] = l(t('view parent product'), "node/$node->parent"); + } } return $links; @@ -184,6 +190,12 @@ function product_load($node) { if ($products[$node->nid] === NULL) { $product = db_fetch_object(db_query('SELECT * FROM {ec_product} WHERE nid = %d', $node->nid)); + + /* unpack the variation fields stored in the ec_product table */ + if ($product->variation) { + $product->variation = unserialize($product->variation); + } + /* Merge the product info for the specific type. */ if ($product_type = module_invoke($product->ptype, 'productapi', $product, 'load')) { foreach ($product_type as $key => $value) { @@ -240,6 +252,12 @@ function product_menu($may_cache) { 'callback' => 'product_to_product', 'access' => user_access('administer products'), 'type' => MENU_LOCAL_TASK, 'weight' => 2); } + + if (db_result(db_query(db_rewrite_sql("SELECT COUNT(n.nid) FROM {ec_product} p INNER JOIN {node} n ON p.nid = n.nid WHERE p.nid = %d AND p.children <> ''"), arg(1))) > 0) { + $items[] = array('path' => 'node/' .arg(1) .'/subproducts', 'title' => t('sub products'), + 'callback' => 'product_sub_products', 'access' => user_access('administer products'), + 'type' => MENU_LOCAL_TASK, 'weight' => 3); + } } } @@ -325,6 +343,28 @@ function product_to_product() { } } +function product_sub_products() { + drupal_set_title(t('view sub products')); + $node = node_load(array('nid' => arg(1))); + $products = product_get_variations($node); + + $headers = array( + array('data' => t('title')), + array('data' => t('operations'), 'colspan' => 2) + ); + + foreach ($products as $nid => $product) { + $rows[] = array( + array('data' => $product->title), + array('data' => l(t('view'), "node/$nid")), + array('data' => l(t('edit'), "node/$nid/edit", array(), drupal_get_destination())), + ); + } + $output.= theme('table', $headers, $rows); + $output.= form_item('', t('%add addition sub products', array('%add' => l(t('add'), "node/add/product/parent/$node->nid", array(), drupal_get_destination())))); + print theme('page', $output); +} + /** * Implementation of hook_node_info(). */ @@ -485,6 +525,9 @@ function product_get_base_form_elements( $ptype = module_invoke($edit->ptype, 'productapi', $edit, 'wizard_select'); $output .= t('

Product type: %ptype

', array('%ptype' => $ptype[$edit->ptype])); } + if ($options = product_get_valid_parents($edit)) { + $output .= form_select(t('Parent Product'), 'parent', $edit->parent, $options); + } $output .= form_textfield(t('Price'), 'price', $edit->price, 25, 50, t('How much does this product retail for? Note: This price may be different from the selling price due to price adjustments elsewhere.'). $help); if (variable_get('payment_recurring', 0)) { $interval = drupal_map_assoc(range(0, 31)); @@ -500,7 +543,6 @@ function product_get_base_form_elements( } $output .= form_textfield(t('SKU'), 'sku', $edit->sku, 25, 50, t('If you have an unique identifier for this product from another system or database, enter that here. This is optional, as system IDs are automatically created for each product.')); $output .= form_radios(t("'Add to cart' link"), 'hide_cart_link', $edit->hide_cart_link, array(t("Visible"), t("Hidden")), t("Choose whether or not you want the 'Add to cart' link visible for this product.")); - return $output; } @@ -511,12 +553,38 @@ function product_get_base_form_elements( */ function product_wizard_form($edit) { + /* check to see if we have a parent product. and load it as the template */ + + if (!$edit && arg(3) == 'parent') { + if ($parent = node_load(array('nid' => arg(4)))) { + if ($ptypes = module_invoke($parent->ptype, 'productapi', $edit, 'subproduct_types')) { + foreach ($parent as $key => $value) { + if (!in_array($key, array('nid', 'status', 'children', 'parent', 'ptype', 'path'))) { + $edit->$key = $parent->$key; + } + if ($edit->type != 'product') { + $edit->type = 'product'; + } + } + if (count($ptypes) == 1) { + $edit->ptype = $ptypes[0]; + } + } + $edit->parent = $parent->nid; + } + } + /* Ask node.module to build the node form and we'll strip off the form tags. */ $node_form = ($edit->uid && $edit->name && $edit->type) ? node_form($edit) : node_add('product'); $node_form = preg_replace("''", '', $node_form); $node_form = preg_replace("''", '', $node_form); - $node_form .= product_form_product_types(); + if ($edit->ptype) { + $node_form .= form_submit(t('Create product')); + } + else { + $node_form .= product_form_product_types(); + } return form($node_form, 'post', null, array('id' => 'node-form')); } @@ -532,6 +600,16 @@ function product_form_product_types() { form_set_error('ptype', t('No product types modules are installed! Please install a product type module such as tangible.module or file.module.')); } else { + /* purge types that are not compatible with the parent product */ + if ($edit->parent) { + $parent = node_load(array('nid' => $edit->parent)); + $subproduct_types = module_invoke($parent->ptype, 'productapi', $edit, 'subproduct_types'); + foreach ($ptypes as $key => $value) { + if (!array_key_exists($key, $subproduct_types)) { + unset($ptypes[$key]); + } + } + } $ptypes_display = $ptypes_display + $ptypes; } $output = form_select(t('Type of product to create'), 'ptype', $edit->ptype, $ptypes_display, t('You cannot change the product type once it\'s created.')); @@ -569,6 +647,9 @@ function product_type_form($edit) { $output .= ''. t('Add to cart link') . ': '. $cart_link . '
'; $output .= ''. t('Title') . ': '. $edit->title . '
'; $output .= ''. t('Product type') . ': '. $edit->ptype . '
'; + if ($options = product_get_valid_parents($edit)) { + $output .= form_select(t('Parent Product'), 'parent', $edit->parent, $options); + } // Display shipping options if we're building a shippable product and we // have the per product shipping option enabled. @@ -639,6 +720,12 @@ function product_form_validate(&$edit) { function product_save($node) { if ($node->ptype) { $node->is_recurring = ($node->price_interval) ? 1 : 0; + + /* simple product variations will be stored in the ec_product table. + * More complex ones may need to be stored in another table. */ + if ($node->variation) { + $node->variation = serialize($node->variation); + } $fields = product_fields(); /* @@ -674,6 +761,23 @@ function product_save($node) { module_invoke(variable_get('shipping_method', 'none'), 'shippingapi', $node, 'per_product_insert'); } } + + /* update the parent product's list of children. */ + + if ($node->parent) { + if ($parent = node_load(array('nid' => $node->parent))) { + if ($parent->children) { + $children = explode(',', $parent->children); + } + else { + $children = array(); + } + if (!in_array($node->nid, $children)) { + $children[] = $node->nid; + db_query('UPDATE {ec_product} SET children = \'%s\' WHERE nid = %d', implode(',', $children), $parent->nid); + } + } + } } } @@ -1035,9 +1139,64 @@ function product_send_recurring_payment_ } /** + * retrieve all the subproducts for a parent product + */ +function product_get_variations($node) { + + /* check and see if this product has been cached */ + if ($data = cache_get("sub-products:{$node->nid}")) { + return unserialize($data->data); + } + else { + $products = array(); + + foreach (explode(',', $node->children) as $nid) { + if ($child = node_load(array('nid' => $nid))) { + $products[$child->nid] = $child; + } + } + + /* store in cache so we can get it quicker */ + cache_set("sub-products:{$node->nid}", serialize($products), time()); + + return $products; + } +} + +/** + * Return an Array of valid parents for the current child. + */ +function product_get_valid_parents($edit) { + if (!$edit->ptype) { + return false; + } + + foreach (module_implements('productapi') as $module) { + $plist = module_invoke($module, 'productapi', $edit, 'subproduct_types', array()); + if (is_array($plist) && in_array($edit->ptype, $plist)) { + $pnames = module_invoke($module, 'productapi', $edit, 'wizard_select', array()); + $ptypes = array_merge((array)$ptypes, array_keys($pnames)); + } + } + + if (isset($ptypes)) { + $options[''] = t('-- parent product --'); + $result = db_query(db_rewrite_sql("SELECT n.nid, n.title FROM {node} n LEFT JOIN {ec_product} p ON n.nid = p.nid WHERE p.parent = 0 AND p.ptype IN ('" .implode("','", $ptypes) ."') ORDER BY n.created")); + + while ($node = db_fetch_object($result)) { + $options[$node->nid] = $node->title; + } + return $options; + } + else { + return false; + } +} + +/** * The names of the database columns in the table. */ function product_fields($table = 'ec_product') { - return array('nid', 'sku', 'price', 'is_recurring', 'price_interval', 'price_unit', 'price_cycle', 'auto_charge', 'ptype', 'hide_cart_link'); + return array('nid', 'sku', 'price', 'is_recurring', 'price_interval', 'price_unit', 'price_cycle', 'auto_charge', 'ptype', 'hide_cart_link', 'parent', 'children', 'children', 'variation'); } ?> Index: product/product.mysql =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/ecommerce/product/product.mysql,v retrieving revision 1.6 diff -u -F^f -r1.6 product.mysql --- product/product.mysql 5 Mar 2005 19:47:54 -0000 1.6 +++ product/product.mysql 13 Nov 2005 05:19:55 -0000 @@ -9,6 +9,9 @@ auto_charge tinyint(3) unsigned NOT NULL default '0', ptype varchar(75) NOT NULL default '', hide_cart_link int(2) unsigned NOT NULL default '0', + parent int(10) NOT NULL default '0', + children varchar(255) NOT NULL default '', + variation longtext NOT NULL, UNIQUE KEY nid (nid), KEY ptype (ptype) ) TYPE=MyISAM; Index: store/store.mysql =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/ecommerce/store/Attic/store.mysql,v retrieving revision 1.16.2.4 diff -u -F^f -r1.16.2.4 store.mysql --- store/store.mysql 2 Oct 2005 18:15:26 -0000 1.16.2.4 +++ store/store.mysql 13 Nov 2005 05:19:55 -0000 @@ -108,6 +108,9 @@ auto_charge tinyint(3) unsigned NOT NULL default '0', ptype varchar(75) NOT NULL default '', hide_cart_link int(2) unsigned NOT NULL default '0', + parent int(10) NOT NULL default '0', + children varchar(255) NOT NULL default '', + variation longtext NOT NULL, UNIQUE KEY nid (nid), KEY ptype (ptype) ) TYPE=MyISAM;