12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169 |
- <?php
- /**
- * @file
- * The product kit module for Ubercart.
- *
- * Product kits are groups of products that are sold as a unit.
- */
- define('UC_PRODUCT_KIT_UNMUTABLE_NO_LIST', -1);
- define('UC_PRODUCT_KIT_UNMUTABLE_WITH_LIST', 0);
- define('UC_PRODUCT_KIT_MUTABLE', 1);
- /**
- * Implements hook_form_FORM_ID_alter() for uc_product_settings_form().
- */
- function uc_product_kit_form_uc_product_settings_form_alter(&$form, &$form_state) {
- $form['product_kit'] = array(
- '#type' => 'fieldset',
- '#title' => 'Product kit settings',
- '#group' => 'product-settings',
- '#weight' => -5,
- );
- $form['product_kit']['uc_product_kit_mutable'] = array(
- '#type' => 'radios',
- '#title' => t('Product kit cart display'),
- '#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' => variable_get('uc_product_kit_mutable', 0),
- );
- }
- /**
- * Implements hook_form_FORM_ID_alter() for node_delete_confirm().
- */
- function uc_product_kit_form_node_delete_confirm_alter(&$form, &$form_state) {
- if (uc_product_is_product((integer)$form['nid']['#value'])) {
- $kits = db_query("SELECT COUNT(k.nid) FROM {node} n JOIN {uc_product_kits} k ON n.vid = k.vid WHERE k.vid IN (SELECT DISTINCT vid FROM {uc_product_kits} WHERE product_id = :nid) GROUP BY k.nid HAVING COUNT(product_id) = 1", array(':nid' => $form['nid']['#value']))->fetchField();
- if ($kits) {
- $description = $form['description']['#markup'];
- $form['description']['#markup'] = 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;
- }
- }
- }
- /**
- * Implements hook_uc_form_alter().
- *
- * Puts 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);
- }
- }
- }
- /**
- * Implements 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'),
- 'base' => 'uc_product_kit',
- 'description' => t('Use <em>product kits</em> to list two or more products together, presenting a logical and convenient grouping of items to the customer.'),
- 'title_label' => t('Name'),
- 'body_label' => t('Description'),
- ),
- );
- }
- /**
- * Implements hook_prepare().
- */
- function uc_product_kit_prepare($node) {
- $defaults = array(
- 'mutable' => variable_get('uc_product_kit_mutable', UC_PRODUCT_KIT_UNMUTABLE_WITH_LIST),
- 'products' => array(),
- 'default_qty' => 1,
- 'ordering' => 0,
- );
- foreach ($defaults as $key => $value) {
- if (!isset($node->$key)) {
- $node->$key = $value;
- }
- }
- }
- /**
- * Implements hook_insert().
- *
- * Adds 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->default_qty = $node->default_qty;
- $obj->ordering = $node->ordering;
- $obj->shippable = FALSE;
- $values = array();
- $placeholders = array();
- foreach ($node->products as $product) {
- if (is_numeric($product)) {
- $product = node_load($product);
- }
- $kit = array(
- 'vid' => $node->vid,
- 'nid' => $node->nid,
- 'product_id' => $product->nid,
- 'mutable' => $node->mutable,
- 'qty' => 1,
- 'synchronized' => 1,
- );
- drupal_write_record('uc_product_kits', $kit);
- $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;
- }
- }
- $obj->model = rtrim($obj->model, ' / ');
- drupal_write_record('uc_products', $obj);
- }
- /**
- * Implements 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;
- if (!isset($node->kit_total) && isset($node->synchronized) && isset($node->sell_price)) {
- $override_discounts = !$node->synchronized;
- $node->kit_total = $node->sell_price;
- }
- else {
- $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 (!isset($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'];
- }
- }
- if (empty($node->revision)) {
- db_delete('uc_product_kits')
- ->condition('vid', $node->vid)
- ->execute();
- }
- foreach ($node->products as $nid) {
- if (is_numeric($nid)) {
- $product = node_load($nid);
- }
- else {
- $product = $nid;
- $nid = $product->nid;
- }
- // 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;
- }
- elseif (isset($node->items[$nid]['discount'])) {
- $discount = (float) $node->items[$nid]['discount'];
- }
- elseif (isset($node->products[$nid]->discount)) {
- $discount = $node->products[$nid]->discount;
- }
- else {
- $discount = 0;
- }
- if (isset($node->items)) {
- if (!isset($node->items[$nid]['qty']) || $node->items[$nid]['qty'] === '') {
- $node->items[$nid]['qty'] = 1;
- }
- $product->qty = $node->items[$nid]['qty'];
- $product->ordering = isset($node->items[$nid]['ordering']) ? $node->items[$nid]['ordering'] : 0;
- }
- else {
- $product->qty = $node->products[$nid]->qty;
- $product->ordering = $node->products[$nid]->ordering;
- }
- // 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;
- }
- $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;
- }
- db_insert('uc_product_kits')
- ->fields(array(
- 'vid' => $node->vid,
- 'nid' => $node->nid,
- 'product_id' => $nid,
- 'mutable' => $node->mutable,
- 'qty' => $product->qty,
- 'discount' => $discount,
- 'ordering' => $product->ordering,
- 'synchronized' => $override_discounts ? 0 : 1,
- ))
- ->execute();
- }
- $obj->model = rtrim($obj->model, ' / ');
- if ($node->mutable == UC_PRODUCT_KIT_MUTABLE && !empty($discount)) {
- drupal_set_message(t('Product kit discounts are not applied because the customer can remove components from their cart.'));
- }
- if (!empty($node->revision)) {
- drupal_write_record('uc_products', $obj);
- }
- else {
- db_merge('uc_products')
- ->key(array('vid' => $obj->vid))
- ->fields(array(
- 'model' => $obj->model,
- 'list_price' => $obj->list_price,
- 'cost' => $obj->cost,
- 'sell_price' => $obj->sell_price,
- 'weight' => $obj->weight,
- 'weight_units' => $obj->weight_units,
- 'default_qty' => $obj->default_qty,
- 'ordering' => $obj->ordering,
- 'shippable' => $obj->shippable ? 1 : 0,
- ))
- ->execute();
- }
- // When a kit is updated, remove matching kits from the cart, as there is no
- // simple way to handle product addition or removal at this point.
- if (module_exists('uc_cart')) {
- db_delete('uc_cart_products')
- ->condition('data', '%' . db_like('s:6:"kit_id";s:' . strlen($node->nid) . ':"' . $node->nid . '";') . '%', 'LIKE')
- ->execute();
- }
- }
- /**
- * Implements hook_delete().
- */
- function uc_product_kit_delete(&$node) {
- if (module_exists('uc_cart')) {
- db_delete('uc_cart_products')
- ->condition('data', '%' . db_like('s:6:"kit_id";s:' . strlen($node->nid) . ':"' . $node->nid . '";') . '%', 'LIKE')
- ->execute();
- }
- db_delete('uc_product_kits')
- ->condition('nid', $node->nid)
- ->execute();
- db_delete('uc_products')
- ->condition('nid', $node->nid)
- ->execute();
- }
- /**
- * Implements hook_load().
- */
- function uc_product_kit_load($nodes) {
- $vids = array();
- foreach ($nodes as $nid => $node) {
- $vids[$nid] = $node->vid;
- }
- $all_products = array();
- $result = db_query("SELECT nid, product_id, mutable, qty, discount, ordering, synchronized FROM {uc_product_kits} WHERE vid IN (:vids) ORDER BY nid, ordering", array(':vids' => $vids));
- while ($prod = $result->fetchObject()) {
- $nodes[$prod->nid]->mutable = $prod->mutable;
- $nodes[$prod->nid]->synchronized = $prod->synchronized;
- // Add the component information.
- $data = array();
- if ($prod->mutable != UC_PRODUCT_KIT_MUTABLE) {
- $data = array('kit_id' => $prod->nid, 'kit_discount' => $prod->discount);
- }
- $product = uc_product_load_variant($prod->product_id, $data);
- $product->qty = $prod->qty;
- $product->discount = $prod->discount;
- $product->ordering = $prod->ordering;
- // Add product to the kit.
- $nodes[$prod->nid]->products[$product->nid] = $product;
- }
- // Add product data to kits.
- uc_product_load($nodes);
- }
- /**
- * Implements hook_module_implements_alter().
- *
- * Ensure that our component products have their discounts applied before any
- * other product alterations are made.
- */
- function uc_product_kit_module_implements_alter(&$implementations, $hook) {
- if ($hook == 'uc_product_alter') {
- $group = $implementations['uc_product_kit'];
- unset($implementations['uc_product_kit']);
- $implementations = array('uc_product_kit' => $group) + $implementations;
- }
- }
- /**
- * Implements hook_theme().
- */
- function uc_product_kit_theme() {
- return array(
- 'uc_product_kit_items_form' => array(
- 'render element' => 'form',
- 'file' => 'uc_product_kit.theme.inc',
- ),
- 'uc_product_kit_add_to_cart' => array(
- 'variables' => array('form' => NULL, 'view_mode' => 'full'),
- 'file' => 'uc_product_kit.theme.inc',
- ),
- 'uc_product_kit_list_item' => array(
- 'arguments' => array('product' => NULL),
- 'file' => 'uc_product_kit.theme.inc',
- ),
- );
- }
- /**
- * Implements hook_node_update().
- *
- * Ensures product kit discounts are updated if their component nodes are
- * updated or deleted.
- */
- function uc_product_kit_node_update($node) {
- $result = db_query("SELECT DISTINCT nid FROM {uc_product_kits} WHERE product_id = :nid", array(':nid' => $node->nid));
- while ($nid = $result->fetchField()) {
- $kit = node_load($nid, NULL, TRUE);
- node_save($kit);
- }
- }
- /**
- * Implements hook_node_delete().
- *
- * Ensures product kit discounts are updated if their component nodes are
- * deleted.
- */
- function uc_product_kit_node_delete($node) {
- $empty = array();
- $result = db_query("SELECT DISTINCT nid FROM {uc_product_kits} WHERE product_id = :nid", array(':nid' => $node->nid));
- while ($nid = $result->fetchField()) {
- $kit = node_load($nid, NULL, TRUE);
- unset($kit->products[$node->nid]);
- if (empty($kit->products)) {
- $empty[] = $kit->nid;
- }
- else {
- node_save($kit);
- }
- }
- if ($empty) {
- node_delete_multiple($empty);
- }
- }
- /**
- * Implements hook_forms().
- *
- * Registers an "Add to Cart" form for each product kit.
- *
- * @see uc_product_kit_add_to_cart_form()
- * @see 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;
- }
- /**
- * Implements hook_form().
- *
- * @ingroup forms
- */
- function uc_product_kit_form(&$node, $form_state) {
- $form['title'] = array(
- '#type' => 'textfield',
- '#title' => t('Name'),
- '#required' => TRUE,
- '#weight' => -5,
- '#default_value' => $node->title,
- '#description' => t('Name of the product kit')
- );
- // 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.
- $products = db_query("SELECT nid, title FROM {node} WHERE type IN (:types) ORDER BY title, nid", array(':types' => $product_types))->fetchAllKeyed();
- $form['base'] = array(
- '#type' => 'fieldset',
- '#title' => t('Product kit information'),
- '#collapsible' => TRUE,
- '#collapsed' => FALSE,
- '#weight' => -10,
- '#group' => 'additional_settings',
- );
- $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' => $node->mutable,
- );
- $form['base']['products'] = array(
- '#type' => 'select',
- '#multiple' => TRUE,
- '#required' => TRUE,
- '#title' => t('Products'),
- '#options' => $products,
- '#default_value' => array_keys($node->products),
- );
- $total = 0;
- $base_total = 0;
- $form['base']['items'] = array(
- '#tree' => TRUE,
- '#theme' => 'uc_product_kit_items_form',
- '#weight' => 1,
- '#description' => t('Enter a positive or negative discount to raise or lower the item price by that amount. The change is applied to each item in the kit.'),
- );
- if (!empty($node->products)) {
- foreach ($node->products as $i => $product) {
- $form['base']['items'][$i] = array(
- '#type' => 'fieldset',
- );
- $form['base']['items'][$i]['link'] = array(
- '#type' => 'item',
- '#markup' => l($product->title, 'node/' . $i),
- );
- $form['base']['items'][$i]['qty'] = array(
- '#type' => 'uc_quantity',
- '#title' => t('Quantity'),
- '#title_display' => 'invisible',
- '#default_value' => $product->qty,
- );
- $form['base']['items'][$i]['ordering'] = array(
- '#type' => 'weight',
- '#title' => t('List position'),
- '#title_display' => 'invisible',
- '#default_value' => isset($product->ordering) ? $product->ordering : 0,
- '#attributes' => array('class' => array('uc-product-kit-item-ordering')),
- );
- $form['base']['items'][$i]['discount'] = array(
- '#type' => 'textfield',
- '#title' => t('Discount'),
- '#title_display' => 'invisible',
- '#field_prefix' => uc_currency_format($product->sell_price) . ' + ',
- '#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, '.', '');
- }
- }
- $form['base']['kit_total'] = array(
- '#type' => 'uc_price',
- '#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_currency_format($total))),
- '#empty_zero' => FALSE,
- );
- }
- if (variable_get('uc_product_add_to_cart_qty', FALSE)) {
- $form['base']['default_qty'] = array(
- '#type' => 'uc_quantity',
- '#title' => t('Default quantity to add to cart'),
- '#default_value' => $node->default_qty,
- '#description' => t('Use 0 to disable the quantity field next to the add to cart button.'),
- '#weight' => 27,
- '#allow_zero' => TRUE,
- );
- }
- else {
- $form['base']['default_qty'] = array(
- '#type' => 'value',
- '#value' => $node->default_qty,
- );
- }
- $form['base']['ordering'] = array(
- '#type' => 'weight',
- '#title' => t('List position'),
- '#description' => t("Specify a value to set this product's position in product lists.<br />Products in the same position will be sorted alphabetically."),
- '#delta' => 25,
- '#default_value' => $node->ordering,
- '#weight' => 30,
- );
- // Disable all shipping related functionality.
- $form['shipping']['#access'] = FALSE;
- return $form;
- }
- /**
- * Implements hook_view().
- */
- function uc_product_kit_view($node, $view_mode) {
- // Give modules a chance to alter this product. If it is a variant, this
- // will have been done already by uc_product_load_variant(), so we check a
- // flag to be sure not to alter twice.
- $variant = empty($node->variant) ? uc_product_load_variant($node->nid) : $node;
- if (module_exists('uc_cart') && empty($variant->data['display_only'])) {
- $add_to_cart_form = drupal_get_form('uc_product_kit_add_to_cart_form_' . $variant->nid, clone $variant);
- if (variable_get('uc_product_update_node_view', FALSE)) {
- $variant = $add_to_cart_form['node']['#value'];
- }
- }
- // Calculate the display price.
- $display_price = 0;
- $suffixes = array();
- if ($node->mutable != UC_PRODUCT_KIT_MUTABLE) {
- // If this is a non-mutable kit, then sum the display price of each of the
- // component products.
- foreach ($variant->products as $product) {
- $build = node_view($product, $view_mode);
- $display_price += $build['display_price']['#value'] * $product->qty;
- $suffixes += $build['display_price']['#suffixes'];
- }
- }
- else {
- // For mutable, just use the price.
- $display_price = $variant->price;
- $suffixes = array();
- }
- $node->content['display_price'] = array(
- '#theme' => 'uc_product_price',
- '#value' => $display_price,
- '#suffixes' => $suffixes,
- '#attributes' => array(
- 'class' => array(
- 'product-kit',
- 'display-price',
- ),
- ),
- );
- $node->content['model'] = array(
- '#theme' => 'uc_product_model',
- '#model' => $variant->model,
- '#view_mode' => $view_mode,
- );
- $node->content['list_price'] = array(
- '#theme' => 'uc_product_price',
- '#title' => t('List price:'),
- '#value' => $variant->list_price,
- '#attributes' => array(
- 'class' => array(
- 'product-kit',
- 'list-price',
- ),
- ),
- );
- $node->content['cost'] = array(
- '#theme' => 'uc_product_price',
- '#title' => t('Cost:'),
- '#value' => $variant->cost,
- '#attributes' => array(
- 'class' => array(
- 'product-kit',
- 'cost',
- ),
- ),
- '#access' => user_access('administer products'),
- );
- $node->content['sell_price'] = array(
- '#theme' => 'uc_product_price',
- '#title' => t('Price:'),
- '#value' => $variant->sell_price,
- '#attributes' => array(
- 'class' => array(
- 'product-kit',
- 'sell-price',
- ),
- ),
- );
- $node->content['weight'] = array(
- '#theme' => 'uc_product_weight',
- '#amount' => $variant->weight,
- '#units' => $variant->weight_units,
- '#view_mode' => $view_mode,
- );
- if ($node->mutable != UC_PRODUCT_KIT_UNMUTABLE_NO_LIST) {
- $node->content['products'] = array('#weight' => 6);
- $i = 0;
- foreach ($node->products as $product) {
- $node->content['products'][$product->nid]['qty'] = array(
- '#markup' => '<div class="product-qty">' . theme('uc_product_kit_list_item', array('product' => $product)) . '</div>',
- );
- $node->content['products'][$product->nid]['#weight'] = $i++;
- }
- }
- if (isset($add_to_cart_form)) {
- $node->content['add_to_cart'] = array(
- '#theme' => 'uc_product_kit_add_to_cart',
- '#view_mode' => $view_mode,
- '#form' => $add_to_cart_form,
- );
- }
- $node->content['#node'] = $variant;
- return $node;
- }
- /**
- * Lets 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'].
- *
- * @see uc_product_kit_add_to_cart_form_validate()
- * @see uc_product_kit_add_to_cart_form_submit()
- * @ingroup forms
- */
- function uc_product_kit_add_to_cart_form($form, &$form_state, $node) {
- $form['nid'] = array('#type' => 'value', '#value' => $node->nid);
- $form['products'] = array('#tree' => TRUE);
- foreach ($node->products as $i => $product) {
- $form['products'][$i] = array('#title' => check_plain($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' => 'uc_quantity',
- '#title' => t('Quantity'),
- '#default_value' => $node->default_qty,
- );
- }
- else {
- $form['qty'] = array('#type' => 'hidden', '#value' => $node->default_qty ? $node->default_qty : 1);
- }
- $form['actions'] = array('#type' => 'actions');
- $form['actions']['submit'] = array(
- '#type' => 'submit',
- '#value' => t('Add to cart'),
- '#id' => 'edit-submit-' . $node->nid,
- '#attributes' => array(
- 'class' => array('node-add-to-cart'),
- ),
- );
- $form['node'] = array(
- '#type' => 'value',
- '#value' => isset($form_state['storage']['variant']) ? $form_state['storage']['variant'] : $node,
- );
- uc_form_alter($form, $form_state, __FUNCTION__);
- return $form;
- }
- /**
- * Form validation handler for uc_product_add_to_cart_form().
- *
- * @see uc_product_kit_add_to_cart_form()
- * @see uc_product_add_to_cart_form_validate()
- */
- function uc_product_kit_add_to_cart_form_validate($form, &$form_state) {
- uc_product_add_to_cart_form_validate($form, $form_state);
- foreach ($form_state['storage']['variant']->products as &$product) {
- $data = module_invoke_all('uc_add_to_cart_data', $form_state['values']['products'][$product->nid]);
- $data += $product->data;
- $qty = $product->qty;
- $product = uc_product_load_variant($product->nid, $data);
- $product->qty = $qty;
- }
- }
- /**
- * Adds 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) {
- if (variable_get('uc_cart_add_item_msg', TRUE)) {
- $node = node_load($form_state['values']['nid']);
- drupal_set_message(t('<strong>@product-title</strong> added to <a href="!url">your shopping cart</a>.', 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.
- *
- * @see uc_product_kit_buy_it_now_form_validate()
- * @see uc_product_kit_buy_it_now_form_submit()
- * @ingroup forms
- */
- function uc_product_kit_buy_it_now_form($form, &$form_state, $node) {
- $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('#title' => check_plain($product->title));
- $form['products'][$i]['nid'] = array('#type' => 'hidden', '#value' => $product->nid);
- $form['products'][$i]['qty'] = array('#type' => 'hidden', '#value' => $product->qty);
- }
- }
- $form['actions'] = array('#type' => 'actions');
- $form['actions']['submit'] = array(
- '#type' => 'submit',
- '#value' => t('Add to cart'),
- '#id' => 'edit-submit-' . $node->nid,
- '#attributes' => array(
- 'class' => array('list-add-to-cart'),
- ),
- );
- uc_form_alter($form, $form_state, __FUNCTION__);
- return $form;
- }
- /**
- * Redirects 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']);
- }
- }
- }
- }
- }
- /**
- * Form submission handler for uc_product_kit_buy_it_now_form().
- *
- * @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));
- }
- /**
- * Implements hook_uc_product_types().
- */
- function uc_product_kit_uc_product_types() {
- return array('product_kit');
- }
- /**
- * Implements hook_uc_store_status().
- */
- function uc_product_kit_uc_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 <a href="!add_url">Add field page</a> and make sure it is set as the Ubercart image in the <a href="!edit_url">content type settings</a> under the Ubercart product settings fieldset.', array('%field_name' => $field, '!add_url' => url('admin/structure/types/manage/product-kit/fields'), '!edit_url' => url('admin/structure/types/manage/product-kit')))));
- }
- }
- }
- /**
- * Implements hook_uc_add_to_cart().
- */
- function uc_product_kit_uc_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('uc_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.
- $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, 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, FALSE);
- }
- }
- // Rebuild the cart items cache.
- uc_cart_get_contents(NULL, 'rebuild');
- return array(array('success' => FALSE, 'silent' => TRUE, 'message' => ''));
- }
- }
- /**
- * Implements hook_uc_product_alter().
- */
- function uc_product_kit_uc_product_alter(&$variant) {
- if (isset($variant->data['kit_id'])) {
- // If this is a kit component load, we would cause infinite recursion trying
- // to node_load() the parent, but we already have the discount available.
- if (isset($variant->data['kit_discount'])) {
- $discount = $variant->data['kit_discount'];
- }
- elseif (($kit = node_load($variant->data['kit_id'])) && $kit->mutable != UC_PRODUCT_KIT_MUTABLE) {
- $discount = $kit->products[$variant->nid]->discount;
- }
- else {
- $discount = 0;
- }
- $variant->price += $discount;
- $variant->data['module'] = 'uc_product_kit';
- }
- }
- /**
- * Implements hook_uc_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_uc_order_product_alter(&$product, $order) {
- if (empty($product->type) || $product->type !== 'product_kit') {
- return;
- }
- // Have to save each individual product if this is a kit.
- foreach ($product->products as $kit_product) {
- $qty = $kit_product->qty * $product->qty;
- $data = isset($kit_product->data) ? $kit_product->data : array();
- $data += module_invoke_all('uc_add_to_cart_data', $_POST['product_controls']['sub_products'][$kit_product->nid]);
- $data['shippable'] = $product->shippable;
- $kit_product = uc_product_load_variant($kit_product->nid, $data);
- $kit_product->qty = $qty;
- drupal_alter('uc_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;
- }
- /**
- * Implements hook_uc_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_uc_cart_display($item) {
- static $elements = array();
- static $products;
- $unique_id = $item->data['unique_id'];
- $kit = node_load($item->data['kit_id']);
- if ($kit->mutable == UC_PRODUCT_KIT_MUTABLE) {
- return uc_product_uc_cart_display($item);
- }
- else {
- if (!isset($products[$unique_id])) {
- // Initialize table row.
- $kit_qty = $item->qty / $kit->products[$item->nid]->qty;
- $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('#markup' => l($kit->title, 'node/' . $kit->nid));
- $element['qty'] = array(
- '#type' => 'uc_quantity',
- '#title' => t('Quantity'),
- '#title_display' => 'invisible',
- '#default_value' => $kit_qty,
- );
- $element['description'] = array('#markup' => '');
- $element['#total'] = 0;
- $element['#suffixes'] = array();
- $element['#extra'] = array();
- $element['#entity'] = $kit; // Override the entity associated with this
- // render-array to be the kit itself.
- $elements[$unique_id] = $element;
- }
- // Add product specific information.
- $extra = uc_product_get_description($item);
- if ($kit->mutable == UC_PRODUCT_KIT_UNMUTABLE_WITH_LIST) {
- $elements[$unique_id]['#extra'][] = array(
- 'data' => theme('uc_product_kit_list_item', array('product' => $item)) . $extra,
- 'class' => array('kit-component-cart-desc'),
- );
- }
- // Build the kit item product variant.
- if (!isset($item->type)) {
- $node = node_load($item->nid);
- $item->type = $node->type;
- }
- $build = node_view($item);
- $elements[$unique_id]['#total'] += $build['display_price']['#value'] * $item->qty;
- $elements[$unique_id]['#suffixes'] += $build['display_price']['#suffixes'];
- $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']['#markup'] .= theme('item_list', array('items' => $elements[$unique_id]['#extra'], 'attributes' => array('class' => array('product-description'))));
- }
- $element = $elements[$unique_id];
- unset($products[$unique_id]);
- unset($elements[$unique_id]);
- return $element;
- }
- }
- return array();
- }
- /**
- * Implements hook_uc_update_cart_item().
- *
- * Handles individual products or entire kits.
- */
- function uc_product_kit_uc_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 (isset($data['kit_id'])) {
- // Product was listed individually.
- uc_product_uc_update_cart_item($nid, $data, $qty, $cid);
- }
- else {
- $kit = node_load($nid);
- foreach ($data as $p_nid => $product) {
- uc_product_uc_update_cart_item($p_nid, $product->data, $qty * $kit->products[$p_nid]->qty, $cid);
- }
- }
- }
- /**
- * Implements hook_views_api().
- */
- function uc_product_kit_views_api() {
- return array(
- 'api' => 3,
- 'path' => drupal_get_path('module', 'uc_product_kit') . '/views',
- );
- }
|