'Product kits', 'description' => 'Configure the product kit settings.', 'access arguments' => array('administer product kits'), 'page callback' => 'drupal_get_form', 'page arguments' => array('uc_product_kit_settings_form'), 'type' => MENU_LOCAL_TASK, 'weight' => 1, 'file' => 'uc_product_kit.admin.inc', ); return $items; } /** * Implementation of hook_perm(). */ function uc_product_kit_perm() { return array('administer product kits', 'create product kits', 'edit own product kits', 'edit all product kits'); } /** * Implementation of hook_access(). */ function uc_product_kit_access($op, $node, $account) { switch ($op) { case 'create': return user_access('create product kits'); case 'update': case 'delete': if ($account->uid == $node->uid) { return user_access('edit own product kits'); } else { return user_access('edit all product kits'); } } } /** * Implementation of hook_form_alter(). */ function uc_product_kit_form_alter(&$form, $form_state, $form_id) { if ($form_id == 'node_delete_confirm' && uc_product_is_product((integer)$form['nid']['#value'])) { $kits = 0; $result = db_query("SELECT k.nid FROM {node} AS n JOIN {uc_product_kits} AS k ON n.vid = k.vid WHERE k.vid IN (SELECT DISTINCT vid FROM {uc_product_kits} WHERE product_id = %d) GROUP BY k.nid HAVING COUNT(product_id) = 1", $form['nid']['#value']); while ($k = db_fetch_object($result)) { $kits++; } if ($kits) { $description = $form['description']['#value']; $form['description']['#value'] = format_plural($kits, 'There is 1 product kit that consists of only this product. It will be deleted as well.', 'There are @count product kits that consist of only this products. They will be deleted as well.') .' '. $description; } } } /** * Implementation of hook_uc_form_alter(). * * Put a product list on the form, so product kit attributes will work on the * order admin edit form. See uc_attribute_form_alter(). */ function uc_product_kit_uc_form_alter(&$form, &$form_state, $form_id) { if ($form_id == 'uc_order_add_product_form') { if (!isset($form['sub_products'])) { // We only want product kits. $kit = $form['node']['#value']; if ($kit->type !== 'product_kit') { return; } $products = array('#tree' => TRUE); foreach ($kit->products as $kit_product) { $products[$kit_product->nid] = array(); } // Add the products to the beginning of the form for visual aesthetics. $form = array_merge(array('sub_products' => $products), $form); } } } /** * Implementation of hook_node_info(). * * @return Node type information for product kits. */ function uc_product_kit_node_info() { return array( 'product_kit' => array( 'name' => t('Product kit'), 'module' => 'uc_product_kit', 'description' => t('This node represents two or more products that have been listed together. This presents a logical and convenient grouping of items to the customer.'), 'title_label' => t('Name'), 'body_label' => t('Description'), ), ); } /** * Implementation of hook_insert(). * * Add a row to {uc_products} to make a product. Extra information about the * component products are stored in {uc_product_kits}. * @param &$node The node object being saved. * @see uc_product_insert() */ function uc_product_kit_insert(&$node) { $obj = new stdClass(); $obj->vid = $node->vid; $obj->nid = $node->nid; $obj->model = ''; $obj->list_price = 0; $obj->cost = 0; $obj->sell_price = 0; $obj->weight = 0; $obj->weight_units = variable_get('uc_weight_unit', 'lb'); $obj->ordering = $node->ordering; $obj->shippable = FALSE; $values = array(); $placeholders = array(); foreach ($node->products as $product) { if (is_numeric($product)) { $product = node_load($product); } $values[] = $node->vid; $values[] = $node->nid; $values[] = $product->nid; $values[] = $node->mutable; $values[] = 1; $values[] = 0; $values[] = 0; $values[] = 1; $placeholders[] = '(%d, %d, %d, %d, %d, %f, %d, %d)'; $obj->model .= $product->model .' / '; $obj->list_price += $product->list_price; $obj->cost += $product->cost; $obj->sell_price += $product->sell_price; $obj->weight += $product->weight * uc_weight_conversion($product->weight_units, $obj->weight_units); if ($product->shippable) { $obj->shippable = TRUE; } } db_query("INSERT INTO {uc_product_kits} (vid, nid, product_id, mutable, qty, discount, ordering, synchronized) VALUES ". implode(',', $placeholders), $values); $obj->model = rtrim($obj->model, ' / '); db_query("INSERT INTO {uc_products} (vid, nid, model, list_price, cost, sell_price, weight, weight_units, default_qty, unique_hash, ordering, shippable) VALUES (%d, %d, '%s', %f, %f, %f, %f, '%s', %d, '%s', %d, %d)", $obj->vid, $obj->nid, $obj->model, $obj->list_price, $obj->cost, $obj->sell_price, $obj->weight, $obj->weight_units, $obj->default_qty, md5($obj->vid . $obj->nid . $obj->model . $obj->list_price . $obj->cost . $obj->sell_price . $obj->weight . $obj->weight_units . $obj->default_qty . $obj->ordering . $obj->shippable . time()), $obj->ordering, $obj->shippable ); } /** * Implementation of hook_update(). * * Updates information in {uc_products} as well as {uc_product_kits}. Because * component products are known when the form is loaded, discount information * can be input and saved. * @param &$node The node to be updated. * @see uc_product_update() */ function uc_product_kit_update(&$node) { $obj = new stdClass(); $obj->vid = $node->vid; $obj->nid = $node->nid; $obj->model = ''; $obj->list_price = 0; $obj->cost = 0; $obj->sell_price = 0; $obj->weight = 0; $obj->weight_units = variable_get('uc_weight_unit', 'lb'); $obj->default_qty = $node->default_qty; $obj->ordering = $node->ordering; $obj->shippable = FALSE; $override_discounts = isset($node->kit_total) && is_numeric($node->kit_total); $product_count = count($node->products); // Get the price of all the products without any discounts. This number is // used if a total kit price was specified to calculate the individual // product discounts. if ($override_discounts) { $base_price = 0; foreach ($node->products as $nid) { // Usually, $node is $form_state['values'] cast as an object. // However, there could be times where node_save() is called with an // actual product kit node. $node->products is an array of objects and // $node->items doesn't exist then. if (is_numeric($nid)) { $product = node_load($nid, NULL, TRUE); if (is_null($node->items[$nid]['qty']) || $node->items[$nid]['qty'] === '') { $node->items[$nid]['qty'] = 1; } } else { $product = $nid; $nid = $product->nid; $node->items[$nid] = (array)$product; } $base_price += $product->sell_price * $node->items[$nid]['qty']; } } $values = array(); $placeholders = array(); foreach ($node->products as $nid) { if (is_numeric($nid)) { $product = node_load($nid); } else { $product = $nid; $nid = $product->nid; } $values[] = $node->vid; $values[] = $node->nid; $values[] = $nid; $values[] = $node->mutable; // When a total kit price is specified, calculate the individual product // discounts needed to reach it, taking into account the product quantities // and their relative prices. More expensive products should be given a // proportionally higher discount. if ($override_discounts) { // After all the algebra that went into finding this formula, it's // surprising how simple it is. $discount = ($node->kit_total - $base_price) * $product->sell_price / $base_price; } else { $discount = $node->items[$nid]['discount']; } if (is_null($node->items[$nid]['qty']) || $node->items[$nid]['qty'] === '') { $node->items[$nid]['qty'] = 1; } $product->qty = $node->items[$nid]['qty']; // Discounts are always saved, but they are only applied if the kit can't // be changed by the customer. if ($node->mutable != UC_PRODUCT_KIT_MUTABLE) { $product->sell_price += $discount; } $values[] = $product->qty; $values[] = $discount; $values[] = $node->items[$nid]['ordering']; $values[] = !$override_discounts; $placeholders[] = '(%d, %d, %d, %d, %d, %f, %d, %d)'; $obj->model .= $product->model .' / '; $obj->list_price += $product->list_price * $product->qty; $obj->cost += $product->cost * $product->qty; $obj->sell_price += $product->sell_price * $product->qty; $obj->weight += $product->weight * $product->qty * uc_weight_conversion($product->weight_units, $obj->weight_units); if ($product->shippable) { $obj->shippable = TRUE; } } $obj->model = rtrim($obj->model, ' / '); if ($node->mutable == UC_PRODUCT_KIT_MUTABLE && $discount) { drupal_set_message(t('Product kit discounts are not applied because the customer can remove components from their cart.')); } if (!$node->revision) { db_query("DELETE FROM {uc_product_kits} WHERE nid = %d", $node->nid); } db_query("INSERT INTO {uc_product_kits} (vid, nid, product_id, mutable, qty, discount, ordering, synchronized) VALUES ". implode(',', $placeholders), $values); if ($node->revision) { db_query("INSERT INTO {uc_products} (vid, nid, model, list_price, cost, sell_price, weight, weight_units, default_qty, unique_hash, ordering, shippable) VALUES (%d, %d, '%s', %f, %f, %f, %f, '%s', %d, '%s', %d, %d)", $obj->vid, $obj->nid, $obj->model, $obj->list_price, $obj->cost, $obj->sell_price, $obj->weight, $obj->weight_units, $obj->default_qty, md5($obj->vid . $obj->nid . $obj->model . $obj->list_price . $obj->cost . $obj->sell_price . $obj->weight . $obj->weight_units . $obj->default_qty . $obj->ordering . time()), $obj->ordering, $obj->shippable ); } else { db_query("UPDATE {uc_products} SET model = '%s', list_price = %f, cost = %f, sell_price = %f, weight = %f, weight_units = '%s', default_qty = %d, ordering = %d, shippable = %d WHERE vid = %d", $obj->model, $obj->list_price, $obj->cost, $obj->sell_price, $obj->weight, $obj->weight_units, $obj->default_qty, $obj->ordering, $obj->shippable, $obj->vid); if (!db_affected_rows()) { db_query("INSERT INTO {uc_products} (vid, nid, model, list_price, cost, sell_price, weight, weight_units, default_qty, unique_hash, ordering, shippable) VALUES (%d, %d, '%s', %f, %f, %f, %f, '%s', %d, '%s', %d, %d)", $obj->vid, $obj->nid, $obj->model, $obj->list_price, $obj->cost, $obj->sell_price, $obj->weight, $obj->weight_units, $obj->default_qty, md5($obj->vid . $obj->nid . $obj->model . $obj->list_price . $obj->cost . $obj->sell_price . $obj->weight . $obj->weight_units . $obj->default_qty . $obj->ordering . $obj->shippable . time()), $obj->ordering, $obj->shippable ); } } } /** * Implementation of hook_delete(). */ function uc_product_kit_delete(&$node) { if (module_exists('uc_cart')) { db_query("DELETE FROM {uc_cart_products} WHERE data LIKE '%%s:6:\"kit_id\";s:%d:\"%s\";%%'", strlen($node->nid), $node->nid); } db_query("DELETE FROM {uc_product_kits} WHERE nid = %d", $node->nid); db_query("DELETE FROM {uc_products} WHERE nid = %d", $node->nid); } /** * Implementation of hook_load(). */ function uc_product_kit_load(&$node) { $obj = new stdClass(); $products = array(); $result = db_query("SELECT product_id, mutable, qty, discount, ordering, synchronized FROM {uc_product_kits} WHERE vid = %d ORDER BY ordering", $node->vid); while ($prod = db_fetch_object($result)) { $obj->mutable = $prod->mutable; $obj->synchronized = $prod->synchronized; $product = node_load($prod->product_id); $product->qty = $prod->qty; $product->discount = $prod->discount; $product->ordering = $prod->ordering; $products[$prod->product_id] = $product; } uasort($products, '_uc_product_kit_sort_products'); $obj->products = $products; if ($extra = uc_product_load($node)) { foreach ($extra as $key => $value) { $obj->$key = $value; } } return $obj; } /** * Implementation of hook_theme(). */ function uc_product_kit_theme() { return array( 'uc_product_kit_add_to_cart' => array( 'arguments' => array('node' => NULL), ), ); } /** * Implementation of hook_nodeapi(). */ function uc_product_kit_nodeapi(&$node, $op, $arg3 = NULL, $arg4 = NULL) { switch ($op) { case 'update': $result = db_query("SELECT DISTINCT nid FROM {uc_product_kits} WHERE product_id = %d", $node->nid); while ($k = db_fetch_object($result)) { $kit = node_load($k->nid, NULL, TRUE); $form_state = array( 'values' => array('nid' => $kit->nid), 'submit_handlers' => array('node_form_submit'), ); drupal_execute('product_kit_node_form', $form_state, $kit); } break; case 'delete': $result = db_query("SELECT DISTINCT nid FROM {uc_product_kits} WHERE product_id = %d", $node->nid); while ($k = db_fetch_object($result)) { $kit = node_load($k->nid, NULL, TRUE); unset($kit->products[$node->nid]); if (empty($kit->products)) { node_delete($kit->nid); } else { $form_state = array( 'values' => array('nid' => $kit->nid), 'submit_handlers' => array('node_form_submit'), ); drupal_execute('product_kit_node_form', $form_state, $kit); } } break; } } /** * Implmentation of hook_forms(). * * Register an "Add to Cart" form for each product kit. * * @see * uc_product_kit_add_to_cart_form() * uc_catalog_buy_it_now_form() */ function uc_product_kit_forms($form_id, $args) { $forms = array(); if (isset($args[0]) && isset($args[0]->nid) && isset($args[0]->type)) { $product = $args[0]; if ($product->type == 'product_kit') { $forms['uc_product_kit_add_to_cart_form_'. $product->nid] = array('callback' => 'uc_product_kit_add_to_cart_form'); $forms['uc_product_add_to_cart_form_'. $product->nid] = array('callback' => 'uc_product_kit_add_to_cart_form'); $forms['uc_catalog_buy_it_now_form_'. $product->nid] = array('callback' => 'uc_product_kit_buy_it_now_form'); } } return $forms; } /** * Implementation of hook_form(). * * @ingroup forms */ function uc_product_kit_form(&$node) { $form = array(); $sign_flag = variable_get('uc_sign_after_amount', FALSE); $currency_sign = variable_get('uc_currency_sign', '$'); $form['title'] = array('#type' => 'textfield', '#title' => t('Name'), '#required' => TRUE, '#weight' => -5, '#default_value' => $node->title, '#description' => t('Name of the product kit') ); $form['body_filter']['body'] = array('#type' => 'textarea', '#title' => t('Description'), '#required' => FALSE, '#default_value' => $node->body, '#rows' => 20, '#description' => t('Explain these whatchamacallits.'), ); $form['body_filter']['format'] = filter_form($node->format); $form['body_filter']['#weight'] = -4; // Create an array of products on the site for use in the product selector. $product_types = uc_product_types(); $products = array(); // Disregard other product kits. unset($product_types[array_search('product_kit', $product_types)]); // Query the database and loop through the results. $result = db_query("SELECT nid, title FROM {node} WHERE type IN ('". implode("','", $product_types) ."') ORDER BY title, nid"); while ($product = db_fetch_object($result)) { // Add each product to the options array. $products[$product->nid] = $product->title; } $form['base'] = array('#type' => 'fieldset', '#title' => t('Product Information'), '#collapsible' => TRUE, '#collapsed' => FALSE, '#weight' => -1, '#attributes' => array('class' => 'product-field'), ); $form['base']['mutable'] = array('#type' => 'radios', '#title' => t('How is this product kit handled by the cart?'), '#options' => array( UC_PRODUCT_KIT_UNMUTABLE_NO_LIST => t('As a unit. Customers may only change how many kits they are buying. Do not list component products.'), UC_PRODUCT_KIT_UNMUTABLE_WITH_LIST => t('As a unit. Customers may only change how many kits they are buying. List component products.'), UC_PRODUCT_KIT_MUTABLE => t('As individual products. Customers may add or remove kit components at will. Discounts entered below are not applied to the kit price'), ), '#default_value' => isset($node->mutable) ? $node->mutable : variable_get('uc_product_kit_mutable', UC_PRODUCT_KIT_UNMUTABLE_WITH_LIST), ); $form['base']['products'] = array('#type' => 'select', '#multiple' => TRUE, '#required' => TRUE, '#title' => t('Products'), '#options' => $products, '#default_value' => isset($node->products) ? array_keys($node->products) : array(), ); $synchronized = FALSE; $total = 0; $base_total = 0; $form['base']['items'] = array('#tree' => TRUE, '#weight' => 1); $context = array( 'revision' => 'formatted', 'type' => 'product', ); if (isset($node->products)) { foreach ($node->products as $i => $product) { $form['base']['items'][$i] = array('#type' => 'fieldset', //'#title' => $product->title, ); $form['base']['items'][$i]['link'] = array('#type' => 'item', '#value' => l($product->title, 'node/'. $i), ); $form['base']['items'][$i]['qty'] = array('#type' => 'textfield', '#title' => t('Quantity'), '#default_value' => $product->qty, '#size' => 5, ); $form['base']['items'][$i]['ordering'] = array('#type' => 'weight', '#title' => t('List position'), '#default_value' => isset($product->ordering) ? $product->ordering : 0, ); $context['field'] = 'sell_price'; $context['subject'] = array( 'node' => $product, ); $form['base']['items'][$i]['discount'] = array('#type' => 'textfield', '#title' => t('Discount'), '#description' => t('Enter a positive or negative value to raise or lower the item price by that amount. This change is applied to each %product in the kit.', array('%product' => $product->title)), '#field_prefix' => t('@price + ', array('@price' => uc_price($product->sell_price, $context))), '#default_value' => isset($product->discount) ? number_format($product->discount, 3, '.', '') : 0, '#size' => 5, ); $total += $product->sell_price * $product->qty; $base_total += $product->sell_price * $product->qty; if (isset($product->discount)) { $total += $product->discount * $product->qty; } } if (!$node->synchronized && $node->sell_price != $total) { // Component products have changed their prices. Recalculate discounts // to keep the same total. $total = $base_total; foreach ($node->products as $i => $product) { $discount = ($node->sell_price - $base_total) * $product->sell_price / $base_total; $total += $discount * $product->qty; $form['base']['items'][$i]['discount']['#default_value'] = number_format($discount, 3, '.', ''); } } $context = array( 'revision' => 'formatted-original', 'type' => 'amount', ); $form['base']['kit_total'] = array( '#type' => 'textfield', '#title' => t('Total price'), '#default_value' => $node->synchronized ? '' : $total, '#description' => t('If this field is set, the discounts of the individual products will be recalculated to equal this value. Currently, the total sell price is %price.', array('%price' => uc_price($total, $context))), '#weight' => 0, '#size' => 20, '#maxlength' => 35, '#field_prefix' => $sign_flag ? '' : $currency_sign, '#field_suffix' => $sign_flag ? $currency_sign : '', ); } $form['base']['default_qty'] = array('#type' => 'textfield', '#title' => t('Default quantity to add to cart'), '#default_value' => !is_null($node->default_qty) ? $node->default_qty : 1, '#description' => t('Leave blank or zero to disable the quantity field in the add to cart form.'), '#weight' => 27, '#size' => 5, '#maxlength' => 6, ); $form['base']['ordering'] = array('#type' => 'weight', '#title' => t('List position'), '#delta' => 25, '#default_value' => isset($node->ordering) ? $node->ordering : 0, '#weight' => 30, ); return $form; } /** * Implementation of hook_view(). */ function uc_product_kit_view($node, $teaser = 0, $page = 0) { $node = node_prepare($node, $teaser); $enabled = variable_get('uc_product_field_enabled', array( 'image' => 1, 'display_price' => 1, 'model' => 1, 'list_price' => 0, 'cost' => 0, 'sell_price' => 1, 'weight' => 0, 'dimensions' => 0, 'add_to_cart' => 1, )); $weight = variable_get('uc_product_field_weight', array( 'image' => -2, 'display_price' => -1, 'model' => 0, 'list_price' => 2, 'cost' => 3, 'sell_price' => 4, 'weight' => 5, 'dimensions' => 6, 'add_to_cart' => 10, )); $context = array( 'revision' => 'themed', 'type' => 'product', 'class' => array( 'product-kit', ), 'subject' => array( 'node' => $node, ), ); if (module_exists('imagecache') && ($field = variable_get('uc_image_product_kit', '')) && isset($node->$field) && file_exists($node->{$field}[0]['filepath'])) { $node->content['image'] = array('#value' => theme('uc_product_image', $node->$field), '#access' => $enabled['image'], '#weight' => $weight['image'], ); } $context['class'][1] = 'display'; $context['field'] = 'sell_price'; $node->content['display_price'] = array('#value' => uc_price($node->sell_price, $context), '#access' => $enabled['display_price'], '#weight' => $weight['display_price'], ); if (!$teaser) { $node->content['model'] = array('#value' => theme('uc_product_model', $node->model), '#access' => $enabled['model'], '#weight' => $weight['model'], ); $node->content['body']['#weight'] = 1; $context['class'][1] = 'list'; $context['field'] = 'list_price'; $node->content['list_price'] = array('#value' => uc_price($node->list_price, $context), '#access' => $enabled['list_price'], '#weight' => $weight['list_price'], ); $context['class'][1] = 'cost'; $context['field'] = 'cost'; $node->content['cost'] = array('#value' => uc_price($node->cost, $context), '#access' => $enabled['cost'] && user_access('administer products'), '#weight' => $weight['cost'], ); } else { $node->content['#attributes'] = array('style' => 'display: inline'); } $context['class'][1] = 'sell'; $context['field'] = 'sell_price'; $node->content['sell_price'] = array('#value' => uc_price($node->sell_price, $context, array('label' => !$teaser)), '#access' => $enabled['sell_price'], '#weight' => $weight['sell_price'], ); if (!$teaser) { $node->content['weight'] = array('#value' => theme('uc_product_weight', $node->weight, $node->weight_units), '#access' => $enabled['weight'], '#weight' => $weight['weight'], ); if ($node->mutable != UC_PRODUCT_KIT_UNMUTABLE_NO_LIST) { $node->content['products'] = array('#weight' => 6); foreach ($node->products as $product) { if (node_access('view', $product)) { $title = l($product->title, 'node/'. $product->nid); } else { $title = check_plain($product->title); } $node->content['products'][$product->nid]['qty'] = array('#value' => '
'. $product->qty .' x '. $title .'
'); $node->content['products'][$product->nid]['#weight'] = $product->ordering; } } if (module_exists('uc_cart')) { $node->content['add_to_cart'] = array('#value' => theme('uc_product_kit_add_to_cart', $node), '#weight' => 10, ); } } elseif (module_exists('uc_cart') && variable_get('uc_product_add_to_cart_teaser', TRUE)) { $node->content['add_to_cart'] = array('#value' => theme('uc_product_kit_add_to_cart', $node), '#weight' => 10, ); } return $node; } /** * Wrap the "Add to Cart" form in a
. * * @ingroup themeable */ function theme_uc_product_kit_add_to_cart($node) { $output = '
'; if ($node->nid) { $output .= drupal_get_form('uc_product_kit_add_to_cart_form_'. $node->nid, $node); } else { $output .= drupal_get_form('uc_product_kit_add_to_cart_form', $node); } $output .= '
'; return $output; } /** * Let the cart know how many of which products are included in a kit. * * uc_attribute_form_alter() hooks into this form to add attributes to each * element in $form['products']. * * @ingroup forms * @see uc_product_kit_add_to_cart_form_submit() */ function uc_product_kit_add_to_cart_form($form_state, $node) { $form = array(); $form['#submit'][] = 'uc_product_kit_add_to_cart_form_submit'; $form['nid'] = array('#type' => 'value', '#value' => $node->nid, ); $form['products'] = array('#tree' => TRUE); foreach ($node->products as $i => $product) { $form['products'][$i] = array(/* '#type' => 'fieldset', */'#title' => $product->title); $form['products'][$i]['nid'] = array('#type' => 'hidden', '#value' => $product->nid); $form['products'][$i]['qty'] = array('#type' => 'hidden', '#value' => $product->qty); } if ($node->default_qty > 0 && variable_get('uc_product_add_to_cart_qty', FALSE)) { $form['qty'] = array('#type' => 'textfield', '#title' => t('Quantity'), '#default_value' => $node->default_qty, '#size' => 5, '#maxlength' => 6, ); } else { $form['qty'] = array('#type' => 'hidden', '#value' => 1); } $form['submit'] = array( '#type' => 'submit', '#value' => variable_get('uc_product_add_to_cart_text', t('Add to cart')), '#id' => 'edit-submit-'. $node->nid, '#attributes' => array( 'class' => 'node-add-to-cart', ), ); $form['node'] = array( '#type' => 'value', '#value' => $node, ); uc_form_alter($form, $form_state, __FUNCTION__); return $form; } /** * Add each product kit's component to the cart in the correct quantities. * * @see uc_product_kit_add_to_cart_form() */ function uc_product_kit_add_to_cart_form_submit($form, &$form_state) { $node = node_load($form_state['values']['nid']); drupal_set_message(t('@product-title added to your shopping cart.', array('@product-title' => $node->title, '!url' => url('cart')))); $form_state['redirect'] = uc_cart_add_item($form_state['values']['nid'], $form_state['values']['qty'], $form_state['values']); } /** * Add to cart button with any extra fields. * * @ingroup forms * @see * uc_product_kit_buy_it_now_form_validate() * uc_product_kit_buy_it_now_form_submit() */ function uc_product_kit_buy_it_now_form($form_state, $node) { $form = array(); $form['#validate'][] = 'uc_product_kit_buy_it_now_form_validate'; $form['#submit'][] = 'uc_product_kit_buy_it_now_form_submit'; $form['nid'] = array( '#type' => 'hidden', '#value' => $node->nid, ); if ($node->type == 'product_kit') { $form['products'] = array('#tree' => TRUE); foreach ($node->products as $i => $product) { $form['products'][$i] = array(/* '#type' => 'fieldset', */'#title' => $product->title); $form['products'][$i]['nid'] = array('#type' => 'hidden', '#value' => $product->nid); $form['products'][$i]['qty'] = array('#type' => 'hidden', '#value' => $product->qty); } } $form['submit'] = array( '#type' => 'submit', '#value' => variable_get('uc_teaser_add_to_cart_text', t('Add to cart')), '#id' => 'edit-submit-'. $node->nid, '#attributes' => array( 'class' => 'list-add-to-cart', ), ); uc_form_alter($form, $form_state, __FUNCTION__); return $form; } /** * Redirect to the product kit page so attributes may be selected. * * @see uc_product_kit_buy_it_now_form() */ function uc_product_kit_buy_it_now_form_validate($form, &$form_state) { if (module_exists('uc_attribute')) { $node = node_load($form_state['values']['nid']); if (is_array($node->products)) { foreach ($node->products as $nid => $product) { $attributes = uc_product_get_attributes($nid); if (!empty($attributes)) { drupal_set_message(t('This product has options that need to be selected before purchase. Please select them in the form below.'), 'error'); drupal_goto('node/'. $form_state['values']['nid']); } } } } } /** * @see uc_product_kit_buy_it_now_form() */ function uc_product_kit_buy_it_now_form_submit($form, &$form_state) { $node = node_load($form_state['values']['nid']); if (module_exists('uc_attribute')) { $attributes = uc_product_get_attributes($node->nid); if (!empty($attributes)) { drupal_set_message(t('This product has options that need to be selected before purchase. Please select them in the form below.'), 'error'); $form_state['redirect'] = drupal_get_path_alias('node/'. $form_state['values']['nid']); return; } if (is_array($node->products)) { foreach ($node->products as $nid => $product) { $attributes = uc_product_get_attributes($nid); if (!empty($attributes)) { drupal_set_message(t('This product has options that need to be selected before purchase. Please select them in the form below.'), 'error'); $form_state['redirect'] = drupal_get_path_alias('node/'. $form_state['values']['nid']); return; } } } } $form_state['redirect'] = uc_cart_add_item($form_state['values']['nid'], 1, $form_state['values'], NULL, variable_get('uc_cart_add_item_msg', TRUE)); } /****************************************************************************** * Ubercart Hooks * ******************************************************************************/ /** * Implementation of hook_product_types(). */ function uc_product_kit_product_types() { return array('product_kit'); } /** * Implementation of hook_store_status(). */ function uc_product_kit_store_status() { if (module_exists('filefield')) { module_load_include('inc', 'content', 'includes/content.crud'); // Check for filefields on products. if ($field = variable_get('uc_image_product_kit', '')) { $instances = content_field_instance_read(array('field_name' => $field, 'type_name' => 'product_kit')); } else { $instances = array(); } if (!count($instances)) { return array(array('status' => 'warning', 'title' => t('Images'), 'desc' => t('Product kits do not have an image field. You may add a %field_name at the Add field page and make sure it is set as the Ubercart image in the content type settings under the Ubercart product settings fieldset.', array('%field_name' => $field, '!add_url' => url('admin/content/node-type/product-kit/fields'), '!edit_url' => url('admin/content/node-type/product-kit'))))); } } } /** * Implementation of hook_add_to_cart(). */ function uc_product_kit_add_to_cart($nid, $qty, $kit_data) { $node = node_load($nid); if ($node->type == 'product_kit') { $cart = uc_cart_get_contents(); $unique = uniqid('', TRUE); $update = array(); $product_data = array(); foreach ($node->products as $product) { $data = array('kit_id' => $node->nid, 'module' => 'uc_product_kit') + module_invoke_all('add_to_cart_data', $kit_data['products'][$product->nid]); $product_data[$product->nid] = $data; foreach ($cart as $item) { if ($item->nid == $product->nid && isset($item->data['kit_id']) && $item->data['kit_id'] == $node->nid) { // There is something in the cart like the product kit. Update // by default, but check that it's possible. if (is_null($add_new)) { $add_new = FALSE; } $data['unique_id'] = $item->data['unique_id']; if ($item->data == $data) { // This product is a candidate for updating the cart quantity. // Make sure the data arrays will compare as equal when serialized. $product_data[$product->nid] = $item->data; $update[$product->nid] = TRUE; } } } } // The product kit can update its items only if they all can be updated. if (count($update) != count($node->products)) { foreach ($node->products as $product) { $data = $product_data[$product->nid]; $data['unique_id'] = $unique; uc_cart_add_item($product->nid, $product->qty * $qty, $data, NULL, FALSE, FALSE); } } else { foreach ($node->products as $product) { $data = $product_data[$product->nid]; uc_cart_add_item($product->nid, $product->qty * $qty, $data, NULL, FALSE, FALSE); } } // Rebuild the cart items cache. uc_cart_get_contents(NULL, 'rebuild'); return array(array('success' => FALSE, 'silent' => TRUE, 'message' => '')); } } /** * Implementation of hook_cart_item(). */ function uc_product_kit_cart_item($op, &$item) { switch ($op) { case 'load': if (isset($item->data['kit_id']) && ($kit = node_load($item->data['kit_id'])) && $kit->mutable != UC_PRODUCT_KIT_MUTABLE) { $kit_discount = $kit->products[$item->nid]->discount; if ($kit_discount !== '') { $item->price += $kit_discount; } } break; } } /** * Implementation of hook_order_product_alter(). * * The hookups for making product kits work on the order edit admin screen. * * @param $product * The order product being saved. * @param $order * The order being edited. */ function uc_product_kit_order_product_alter(&$product, $order) { if ($product->type !== 'product_kit') { return; } // Have to save each individual product if this is a kit. foreach ($product->products as $kit_product) { $kit_product->price = $kit_product->sell_price; $kit_product->qty *= $product->qty; // Load all the attributes/discounts/etc as if we were adding it to the // cart. $kit_product->data = module_invoke_all('add_to_cart_data', $_POST['sub_products'][$kit_product->nid]); $kit_product->data['kit_id'] = $product->nid; // Run the product through the alter mill. foreach (module_list() as $module) { $function = $module .'_cart_item'; if (function_exists($function)) { // $product must be passed by reference. $function('load', $kit_product); } } $price_info = array( 'price' => $kit_product->price, 'qty' => 1, ); $context = array( 'revision' => 'altered', 'type' => 'order_product', 'subject' => array( 'order' => $order, 'product' => $kit_product, 'node' => node_load($kit_product->nid), ), ); $kit_product->price = uc_price($price_info, $context); drupal_alter('order_product', $kit_product, $order); // Save the individual item to the order. uc_order_product_save($order->order_id, $kit_product); } // Don't save the base kit node, though. $product->skip_save = TRUE; } /** * Implementation of hook_cart_display(). * * Displays either the kit as a whole, or each individual product based on the * store configuration. Each product in the cart that was added by uc_product_kit * was also given a unique kit id in order to help prevent collisions. The side * effect is that identical product kits are listed separately if added separately. * The customer may still change the quantity of kits like other products. * * @param $item * An item in the shopping cart. * @return * A form element array to be processed by uc_cart_view_form(). */ function uc_product_kit_cart_display($item) { static $elements = array(); static $products; $unique_id = $item->data['unique_id']; $kit = node_load($item->data['kit_id']); //print '
'. print_r($kit, TRUE) .'
'; if ($kit->mutable == UC_PRODUCT_KIT_MUTABLE) { return uc_product_cart_display($item); } else { if (!isset($products[$unique_id])) { // Initialize table row $element = array(); $element['nid'] = array('#type' => 'value', '#value' => $kit->nid); $element['module'] = array('#type' => 'value', '#value' => 'uc_product_kit'); $element['remove'] = array('#type' => 'submit', '#value' => t('Remove')); $element['title'] = array('#value' => l($kit->title, 'node/'. $kit->nid)); $element['qty'] = array( '#type' => 'textfield', '#default_value' => $item->qty / $kit->products[$item->nid]->qty, '#size' => 5, '#maxlength' => 6, ); $element['#total'] = 0; $element['#extra'] = array(); $elements[$unique_id] = $element; } // Add product specific information $extra = uc_product_get_description($item); if (node_access('view', node_load($item->nid))) { $title = l($item->title, 'node/'. $item->nid); } else { $title = $item->title; } if ($kit->mutable == UC_PRODUCT_KIT_UNMUTABLE_WITH_LIST) { $elements[$unique_id]['#extra'][] = array('data' => $item->qty .' x '. $title . $extra, 'class' => 'kit-component-cart-desc'); } $context = array( 'revision' => 'altered', 'type' => 'cart_item', 'field' => 'price', 'subject' => array( 'cart_item' => $item, 'node' => $kit->products[$item->nid], 'kit' => $kit, ), ); $price_info = array( 'price' => $item->price, 'qty' => $item->qty, ); $elements[$unique_id]['#total'] += uc_price($price_info, $context); $elements[$unique_id]['data'][$item->nid] = $item; $products[$unique_id][] = $item->nid; // Check if all products in this kit have been accounted for. $done = TRUE; foreach ($kit->products as $product) { if (!in_array($product->nid, $products[$unique_id])) { $done = FALSE; break; } } if ($done) { drupal_add_css(drupal_get_path('module', 'uc_product_kit') .'/uc_product_kit.css'); $elements[$unique_id]['data'] = array('#type' => 'value', '#value' => serialize($elements[$unique_id]['data'])); if ($kit->mutable == UC_PRODUCT_KIT_UNMUTABLE_WITH_LIST) { $elements[$unique_id]['description']['#value'] .= theme('item_list', $elements[$unique_id]['#extra'], NULL, 'ul', array('class' => 'product-description')); } $element = $elements[$unique_id]; unset($products[$unique_id]); unset($elements[$unique_id]); return $element; } } return array(); } /** * Implementation of hook_update_cart_item(). * * Handles individual products or entire kits. */ function uc_product_kit_update_cart_item($nid, $data = array(), $qty, $cid = NULL) { if (!$nid) return NULL; $cid = !(is_null($cid) || empty($cid)) ? $cid : uc_cart_get_id(); if ($qty < 1) { foreach ($data as $p_nid => $product) { uc_cart_remove_item($p_nid, $cid, $product->data); } } else { if (isset($data['kit_id'])) { // Product was listed individually uc_product_update_cart_item($nid, $data, $qty, $cid); } else { $kit = node_load($nid); foreach ($data as $p_nid => $product) { uc_product_update_cart_item($p_nid, $product->data, $qty * $kit->products[$p_nid]->qty, $cid); } } } } /** * usort() callback. */ function _uc_product_kit_sort_products($a, $b) { if ($a->ordering == $b->ordering) { return 0; } else { return ($a->ordering < $b->ordering) ? -1 : 1; } }