Guest User

uc_product_kit.module

a guest
Aug 7th, 2012
266
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 40.11 KB | None | 0 0
  1. <?php
  2.  
  3. /**
  4.  * @file
  5.  * The product kit module for Ubercart.
  6.  *
  7.  * Product kits are groups of products that are sold as a unit.
  8.  */
  9.  
  10. define('UC_PRODUCT_KIT_UNMUTABLE_NO_LIST', -1);
  11. define('UC_PRODUCT_KIT_UNMUTABLE_WITH_LIST', 0);
  12. define('UC_PRODUCT_KIT_MUTABLE', 1);
  13.  
  14.  
  15. /**
  16.  * Implements hook_permission().
  17.  */
  18. function uc_product_kit_permission() {
  19.   return array(
  20.     'administer product kits' => array(
  21.       'title' => t('Administer product kits'),
  22.     ),
  23.   );
  24. }
  25.  
  26. /**
  27.  * Implements hook_form_FORM_ID_alter() for uc_product_settings_form().
  28.  */
  29. function uc_product_kit_form_uc_product_settings_form_alter(&$form, &$form_state) {
  30.   $form['product_kit'] = array(
  31.     '#type' => 'fieldset',
  32.     '#title' => 'Product kit settings',
  33.     '#group' => 'product-settings',
  34.     '#weight' => -5,
  35.   );
  36.   $form['product_kit']['uc_product_kit_mutable'] = array(
  37.     '#type' => 'radios',
  38.     '#title' => t('Product kit cart display'),
  39.     '#options' => array(
  40.       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.'),
  41.       UC_PRODUCT_KIT_UNMUTABLE_WITH_LIST => t('As a unit. Customers may only change how many kits they are buying. List component products.'),
  42.       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'),
  43.     ),
  44.     '#default_value' => variable_get('uc_product_kit_mutable', 0),
  45.   );
  46. }
  47.  
  48. /**
  49.  * Implements hook_form_FORM_ID_alter() for node_delete_confirm().
  50.  */
  51. function uc_product_kit_form_node_delete_confirm_alter(&$form, &$form_state) {
  52.   if (uc_product_is_product((integer)$form['nid']['#value'])) {
  53.     $kits = db_query("SELECT COUNT(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 = :nid) GROUP BY k.nid HAVING COUNT(product_id) = 1", array(':nid' => $form['nid']['#value']))->fetchField();
  54.  
  55.     if ($kits) {
  56.       $description = $form['description']['#markup'];
  57.       $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;
  58.     }
  59.   }
  60. }
  61.  
  62. /**
  63.  * Implements hook_uc_form_alter().
  64.  *
  65.  * Puts a product list on the form, so product kit attributes will work on the
  66.  * order admin edit form. See uc_attribute_form_alter().
  67.  */
  68. function uc_product_kit_uc_form_alter(&$form, &$form_state, $form_id) {
  69.   if ($form_id == 'uc_order_add_product_form') {
  70.     if (!isset($form['sub_products'])) {
  71.       // We only want product kits.
  72.       $kit = $form['node']['#value'];
  73.       if ($kit->type !== 'product_kit') {
  74.         return;
  75.       }
  76.  
  77.       $products = array('#tree' => TRUE);
  78.       foreach ($kit->products as $kit_product) {
  79.         $products[$kit_product->nid] = array();
  80.       }
  81.  
  82.       // Add the products to the beginning of the form for visual aesthetics.
  83.       $form = array_merge(array('sub_products' => $products), $form);
  84.     }
  85.   }
  86. }
  87.  
  88. /**
  89.  * Implements hook_node_info().
  90.  *
  91.  * @return
  92.  *   Node type information for product kits.
  93.  */
  94. function uc_product_kit_node_info() {
  95.   return array(
  96.     'product_kit' => array(
  97.       'name' => t('Product kit'),
  98.       'base' => 'uc_product_kit',
  99.       '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.'),
  100.       'title_label' => t('Name'),
  101.       'body_label' => t('Description'),
  102.     ),
  103.   );
  104. }
  105.  
  106. /**
  107.  * Implements hook_prepare().
  108.  */
  109. function uc_product_kit_prepare($node) {
  110.   $defaults = array(
  111.     'mutable' => variable_get('uc_product_kit_mutable', UC_PRODUCT_KIT_UNMUTABLE_WITH_LIST),
  112.     'products' => array(),
  113.     'default_qty' => 1,
  114.     'ordering' => 0,
  115.   );
  116.  
  117.   foreach ($defaults as $key => $value) {
  118.     if (!isset($node->$key)) {
  119.       $node->$key = $value;
  120.     }
  121.   }
  122. }
  123.  
  124. /**
  125.  * Implements hook_insert().
  126.  *
  127.  * Adds a row to {uc_products} to make a product. Extra information about the
  128.  * component products are stored in {uc_product_kits}.
  129.  *
  130.  * @param &$node
  131.  *   The node object being saved.
  132.  *
  133.  * @see uc_product_insert()
  134.  */
  135. function uc_product_kit_insert(&$node) {
  136.   $obj = new stdClass();
  137.   $obj->vid = $node->vid;
  138.   $obj->nid = $node->nid;
  139.   $obj->model = '';
  140.   $obj->list_price = 0;
  141.   $obj->cost = 0;
  142.   $obj->sell_price = 0;
  143.   $obj->weight = 0;
  144.   $obj->weight_units = variable_get('uc_weight_unit', 'lb');
  145.   $obj->default_qty = 0;
  146.   $obj->ordering = $node->ordering;
  147.   $obj->shippable = FALSE;
  148.  
  149.   $values = array();
  150.   $placeholders = array();
  151.   foreach ($node->products as $product) {
  152.     if (is_numeric($product)) {
  153.       $product = node_load($product);
  154.     }
  155.  
  156.     $kit = array(
  157.       'vid' => $node->vid,
  158.       'nid' => $node->nid,
  159.       'product_id' => $product->nid,
  160.       'mutable' => $node->mutable,
  161.       'qty' => 1,
  162.       'synchronized' => 1,
  163.     );
  164.  
  165.     drupal_write_record('uc_product_kits', $kit);
  166.  
  167.     $obj->model .= $product->model . ' / ';
  168.     $obj->list_price += $product->list_price;
  169.     $obj->cost += $product->cost;
  170.     $obj->sell_price += $product->sell_price;
  171.     $obj->weight += $product->weight * uc_weight_conversion($product->weight_units, $obj->weight_units);
  172.     if ($product->shippable) {
  173.       $obj->shippable = TRUE;
  174.     }
  175.   }
  176.  
  177.   $obj->model = rtrim($obj->model, ' / ');
  178.   $obj->unique_hash = 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 . REQUEST_TIME);
  179.  
  180.   drupal_write_record('uc_products', $obj);
  181. }
  182.  
  183. /**
  184.  * Implements hook_update().
  185.  *
  186.  * Updates information in {uc_products} as well as {uc_product_kits}. Because
  187.  * component products are known when the form is loaded, discount information
  188.  * can be input and saved.
  189.  *
  190.  * @param &$node
  191.  *   The node to be updated.
  192.  *
  193.  * @see uc_product_update()
  194.  */
  195. function uc_product_kit_update(&$node) {
  196.   $obj = new stdClass();
  197.   $obj->vid = $node->vid;
  198.   $obj->nid = $node->nid;
  199.   $obj->model = '';
  200.   $obj->list_price = 0;
  201.   $obj->cost = 0;
  202.   $obj->sell_price = 0;
  203.   $obj->weight = 0;
  204.   $obj->weight_units = variable_get('uc_weight_unit', 'lb');
  205.   $obj->default_qty = $node->default_qty;
  206.   $obj->ordering = $node->ordering;
  207.   $obj->shippable = FALSE;
  208.  
  209.   if (!isset($node->kit_total) && isset($node->synchronized) && isset($node->sell_price)) {
  210.     $override_discounts = !$node->synchronized;
  211.     $node->kit_total = $node->sell_price;
  212.   }
  213.   else {
  214.     $override_discounts = isset($node->kit_total) && is_numeric($node->kit_total);
  215.   }
  216.   $product_count = count($node->products);
  217.  
  218.   // Get the price of all the products without any discounts. This number is
  219.   // used if a total kit price was specified to calculate the individual
  220.   // product discounts.
  221.   if ($override_discounts) {
  222.     $base_price = 0;
  223.     foreach ($node->products as $nid) {
  224.       // Usually, $node is $form_state['values'] cast as an object.
  225.       // However, there could be times where node_save() is called with an
  226.       // actual product kit node. $node->products is an array of objects and
  227.       // $node->items doesn't exist then.
  228.       if (is_numeric($nid)) {
  229.         $product = node_load($nid, NULL, TRUE);
  230.         if (!isset($node->items[$nid]['qty']) || $node->items[$nid]['qty'] === '') {
  231.           $node->items[$nid]['qty'] = 1;
  232.         }
  233.       }
  234.       else {
  235.         $product = $nid;
  236.         $nid = $product->nid;
  237.         $node->items[$nid] = (array)$product;
  238.       }
  239.       $base_price += $product->sell_price * $node->items[$nid]['qty'];
  240.     }
  241.   }
  242.  
  243.   if (empty($node->revision)) {
  244.     db_delete('uc_product_kits')
  245.       ->condition('vid', $node->vid)
  246.       ->execute();
  247.   }
  248.   foreach ($node->products as $nid) {
  249.     if (is_numeric($nid)) {
  250.       $product = node_load($nid);
  251.     }
  252.     else {
  253.       $product = $nid;
  254.       $nid = $product->nid;
  255.     }
  256.  
  257.     // When a total kit price is specified, calculate the individual product
  258.     // discounts needed to reach it, taking into account the product quantities
  259.     // and their relative prices. More expensive products should be given a
  260.     // proportionally higher discount.
  261.     if ($override_discounts) {
  262.       // After all the algebra that went into finding this formula, it's
  263.       // surprising how simple it is.
  264.       $discount = ($node->kit_total - $base_price) * $product->sell_price / $base_price;
  265.     }
  266.     elseif (isset($node->items[$nid]['discount'])) {
  267.       $discount = (float) $node->items[$nid]['discount'];
  268.     }
  269.     else {
  270.       $discount = $node->products[$nid]->discount;
  271.     }
  272.  
  273.     if (isset($node->items)) {
  274.       if (!isset($node->items[$nid]['qty']) || $node->items[$nid]['qty'] === '') {
  275.         $node->items[$nid]['qty'] = 1;
  276.       }
  277.  
  278.       $product->qty = $node->items[$nid]['qty'];
  279.     }
  280.     else {
  281.       $product->qty = $node->products[$nid]->qty;
  282.     }
  283.     // Discounts are always saved, but they are only applied if the kit can't
  284.     // be changed by the customer.
  285.     if ($node->mutable != UC_PRODUCT_KIT_MUTABLE) {
  286.       $product->sell_price += $discount;
  287.     }
  288.  
  289.     $obj->model .= $product->model . ' / ';
  290.     $obj->list_price += $product->list_price * $product->qty;
  291.     $obj->cost += $product->cost * $product->qty;
  292.     $obj->sell_price += $product->sell_price * $product->qty;
  293.     $obj->weight += $product->weight * $product->qty * uc_weight_conversion($product->weight_units, $obj->weight_units);
  294.     if ($product->shippable) {
  295.       $obj->shippable = TRUE;
  296.     }
  297.  
  298.     db_insert('uc_product_kits')
  299.       ->fields(array(
  300.         'vid' => $node->vid,
  301.         'nid' => $node->nid,
  302.         'product_id' => $nid,
  303.         'mutable' => $node->mutable,
  304.         'qty' => $product->qty,
  305.         'discount' => $discount,
  306.         'ordering' => isset($node->items[$nid]['ordering']) ? $node->items[$nid]['ordering'] : 0,
  307.         'synchronized' => $override_discounts ? 0 : 1,
  308.       ))
  309.       ->execute();
  310.   }
  311.  
  312.   $obj->model = rtrim($obj->model, ' / ');
  313.  
  314.   if ($node->mutable == UC_PRODUCT_KIT_MUTABLE && !empty($discount)) {
  315.     drupal_set_message(t('Product kit discounts are not applied because the customer can remove components from their cart.'));
  316.   }
  317.  
  318.   if (!empty($node->revision)) {
  319.     $obj->unique_hash = 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 . REQUEST_TIME);
  320.     drupal_write_record('uc_products', $obj);
  321.   }
  322.   else {
  323.     db_merge('uc_products')
  324.       ->key(array('vid' => $obj->vid))
  325.       ->fields(array(
  326.         'model' => $obj->model,
  327.         'list_price' => $obj->list_price,
  328.         'cost' => $obj->cost,
  329.         'sell_price' => $obj->sell_price,
  330.         'weight' => $obj->weight,
  331.         'weight_units' => $obj->weight_units,
  332.         'default_qty' => $obj->default_qty,
  333.         'ordering' => $obj->ordering,
  334.         'shippable' => $obj->shippable ? 1 : 0,
  335.       ))
  336.       ->insertFields(array(
  337.         'unique_hash' => 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 . REQUEST_TIME),
  338.       ))
  339.       ->execute();
  340.   }
  341. }
  342.  
  343. /**
  344.  * Implements hook_delete().
  345.  */
  346. function uc_product_kit_delete(&$node) {
  347.   if (module_exists('uc_cart')) {
  348.     db_delete('uc_cart_products')
  349.       ->condition('data', '%' . db_like('s:6:"kit_id";s:' . strlen($node->nid) . ':"' . $node->nid . '";') . '%', 'LIKE')
  350.       ->execute();
  351.   }
  352.   db_delete('uc_product_kits')
  353.     ->condition('nid', $node->nid)
  354.     ->execute();
  355.  
  356.   db_delete('uc_products')
  357.     ->condition('nid', $node->nid)
  358.     ->execute();
  359. }
  360.  
  361. /**
  362.  * Implements hook_load().
  363.  */
  364. function uc_product_kit_load($nodes) {
  365.   $vids = array();
  366.   foreach ($nodes as $nid => $node) {
  367.     $vids[$nid] = $node->vid;
  368.   }
  369.  
  370.   $all_products = array();
  371.   $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));
  372.   while ($prod = $result->fetchObject()) {
  373.     $nodes[$prod->nid]->mutable = $prod->mutable;
  374.     $nodes[$prod->nid]->synchronized = $prod->synchronized;
  375.  
  376.     // Add the component information.
  377.     $data = array();
  378.     if ($prod->mutable != UC_PRODUCT_KIT_MUTABLE) {
  379.       $data = array('kit_id' => $prod->nid, 'kit_discount' => $prod->discount);
  380.     }
  381.     $product = uc_product_load_variant($prod->product_id, $data);
  382.     $product->qty = $prod->qty;
  383.     $product->discount = $prod->discount;
  384.     $product->ordering = $prod->ordering;
  385.  
  386.     // Add product to the kit.
  387.     $nodes[$prod->nid]->products[$product->nid] = $product;
  388.   }
  389.  
  390.   // Add product data to kits.
  391.   uc_product_load($nodes);
  392. }
  393.  
  394. /**
  395.  * Implements hook_module_implements_alter().
  396.  *
  397.  * Ensure that our component products have their discounts applied before any
  398.  * other product alterations are made.
  399.  */
  400. function uc_product_kit_module_implements_alter(&$implementations, $hook) {
  401.   if ($hook == 'uc_product_alter') {
  402.     $group = $implementations['uc_product_kit'];
  403.     unset($implementations['uc_product_kit']);
  404.     $implementations =  array('uc_product_kit' => $group) + $implementations;
  405.   }
  406. }
  407.  
  408. /**
  409.  * Implements hook_theme().
  410.  */
  411. function uc_product_kit_theme() {
  412.   return array(
  413.     'uc_product_kit_items_form' => array(
  414.       'render element' => 'form',
  415.       'file' => 'uc_product_kit.theme.inc',
  416.     ),
  417.     'uc_product_kit_add_to_cart' => array(
  418.       'variables' => array('form' => NULL, 'view_mode' => 'full'),
  419.       'file' => 'uc_product_kit.theme.inc',
  420.     ),
  421.     'uc_product_kit_list_item' => array(
  422.       'arguments' => array('product' => NULL),
  423.       'file' => 'uc_product_kit.theme.inc',
  424.     ),
  425.   );
  426. }
  427.  
  428. /**
  429.  * Implements hook_node_update().
  430.  *
  431.  * Ensures product kit discounts are updated if their component nodes are
  432.  * updated or deleted.
  433.  */
  434. function uc_product_kit_node_update($node) {
  435.   $result = db_query("SELECT DISTINCT nid FROM {uc_product_kits} WHERE product_id = :nid", array(':nid' => $node->nid));
  436.   while ($nid = $result->fetchField()) {
  437.     $kit = node_load($nid, NULL, TRUE);
  438.     node_save($kit);
  439.   }
  440. }
  441.  
  442. /**
  443.  * Implements hook_node_delete().
  444.  *
  445.  * Ensures product kit discounts are updated if their component nodes are
  446.  * deleted.
  447.  */
  448. function uc_product_kit_node_delete($node) {
  449.   $empty = array();
  450.   $result = db_query("SELECT DISTINCT nid FROM {uc_product_kits} WHERE product_id = :nid", array(':nid' => $node->nid));
  451.   while ($nid = $result->fetchField()) {
  452.     $kit = node_load($nid, NULL, TRUE);
  453.     unset($kit->products[$node->nid]);
  454.  
  455.     if (empty($kit->products)) {
  456.       $empty[] = $kit->nid;
  457.     }
  458.     else {
  459.       node_save($kit);
  460.     }
  461.   }
  462.  
  463.   if ($empty) {
  464.     node_delete_multiple($empty);
  465.   }
  466. }
  467.  
  468. /**
  469.  * Implements hook_forms().
  470.  *
  471.  * Registers an "Add to Cart" form for each product kit.
  472.  *
  473.  * @see uc_product_kit_add_to_cart_form()
  474.  * @see uc_catalog_buy_it_now_form()
  475.  */
  476. function uc_product_kit_forms($form_id, $args) {
  477.   $forms = array();
  478.   if (isset($args[0]) && isset($args[0]->nid) && isset($args[0]->type)) {
  479.     $product = $args[0];
  480.     if ($product->type == 'product_kit') {
  481.       $forms['uc_product_kit_add_to_cart_form_' . $product->nid] = array('callback' => 'uc_product_kit_add_to_cart_form');
  482.       $forms['uc_product_add_to_cart_form_' . $product->nid] = array('callback' => 'uc_product_kit_add_to_cart_form');
  483.       $forms['uc_catalog_buy_it_now_form_' . $product->nid] = array('callback' => 'uc_product_kit_buy_it_now_form');
  484.     }
  485.   }
  486.   return $forms;
  487. }
  488.  
  489. /**
  490.  * Implements hook_form().
  491.  *
  492.  * @ingroup forms
  493.  */
  494. function uc_product_kit_form(&$node, $form_state) {
  495.   $form['title'] = array(
  496.     '#type' => 'textfield',
  497.     '#title' => t('Name'),
  498.     '#required' => TRUE,
  499.     '#weight' => -5,
  500.     '#default_value' => $node->title,
  501.     '#description' => t('Name of the product kit')
  502.   );
  503.  
  504.   // Create an array of products on the site for use in the product selector.
  505.   $product_types = uc_product_types();
  506.   $products = array();
  507.  
  508.   // Disregard other product kits.
  509.   unset($product_types[array_search('product_kit', $product_types)]);
  510.  
  511.   // Query the database and loop through the results.
  512.   $products = db_query("SELECT nid, title FROM {node} WHERE type IN (:types) ORDER BY title, nid", array(':types' => $product_types))->fetchAllKeyed();
  513.  
  514.   $form['base'] = array(
  515.     '#type' => 'fieldset',
  516.     '#title' => t('Product kit information'),
  517.     '#collapsible' => TRUE,
  518.     '#collapsed' => FALSE,
  519.     '#weight' => -10,
  520.     '#group' => 'additional_settings',
  521.   );
  522.   $form['base']['mutable'] = array(
  523.     '#type' => 'radios',
  524.     '#title' => t('How is this product kit handled by the cart?'),
  525.     '#options' => array(
  526.       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.'),
  527.       UC_PRODUCT_KIT_UNMUTABLE_WITH_LIST => t('As a unit. Customers may only change how many kits they are buying. List component products.'),
  528.       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'),
  529.     ),
  530.     '#default_value' => $node->mutable,
  531.   );
  532.   $form['base']['products'] = array(
  533.     '#type' => 'select',
  534.     '#multiple' => TRUE,
  535.     '#required' => TRUE,
  536.     '#title' => t('Products'),
  537.     '#options' => $products,
  538.     '#default_value' => array_keys($node->products),
  539.   );
  540.   $total = 0;
  541.   $base_total = 0;
  542.   $form['base']['items'] = array(
  543.     '#tree' => TRUE,
  544.     '#theme' => 'uc_product_kit_items_form',
  545.     '#weight' => 1,
  546.     '#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.'),
  547.   );
  548.   if (!empty($node->products)) {
  549.     foreach ($node->products as $i => $product) {
  550.       $form['base']['items'][$i] = array(
  551.         '#type' => 'fieldset',
  552.       );
  553.       $form['base']['items'][$i]['link'] = array(
  554.         '#type' => 'item',
  555.         '#markup' => l($product->title, 'node/' . $i),
  556.       );
  557.       $form['base']['items'][$i]['qty'] = array(
  558.         '#type' => 'uc_quantity',
  559.         '#title' => t('Quantity'),
  560.         '#title_display' => 'invisible',
  561.         '#default_value' => $product->qty,
  562.       );
  563.       $form['base']['items'][$i]['ordering'] = array(
  564.         '#type' => 'weight',
  565.         '#title' => t('List position'),
  566.         '#title_display' => 'invisible',
  567.         '#default_value' => isset($product->ordering) ? $product->ordering : 0,
  568.         '#attributes' => array('class' => array('uc-product-kit-item-ordering')),
  569.       );
  570.  
  571.       $form['base']['items'][$i]['discount'] = array(
  572.         '#type' => 'textfield',
  573.         '#title' => t('Discount'),
  574.         '#title_display' => 'invisible',
  575.         '#field_prefix' => uc_currency_format($product->sell_price) . ' + ',
  576.         '#default_value' => isset($product->discount) ? number_format($product->discount, 3, '.', '') : 0,
  577.         '#size' => 5,
  578.       );
  579.       $total += $product->sell_price * $product->qty;
  580.       $base_total += $product->sell_price * $product->qty;
  581.       if (isset($product->discount)) {
  582.         $total += $product->discount * $product->qty;
  583.       }
  584.     }
  585.     if (!$node->synchronized && $node->sell_price != $total) {
  586.       // Component products have changed their prices. Recalculate discounts
  587.       // to keep the same total.
  588.       $total = $base_total;
  589.       foreach ($node->products as $i => $product) {
  590.         $discount = ($node->sell_price - $base_total) * $product->sell_price / $base_total;
  591.         $total += $discount * $product->qty;
  592.         $form['base']['items'][$i]['discount']['#default_value'] = number_format($discount, 3, '.', '');
  593.       }
  594.     }
  595.     $form['base']['kit_total'] = array(
  596.       '#type' => 'uc_price',
  597.       '#title' => t('Total price'),
  598.       '#default_value' => $node->synchronized ? '' : $total,
  599.       '#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))),
  600.       '#empty_zero' => FALSE,
  601.     );
  602.   }
  603.  
  604.   if (variable_get('uc_product_add_to_cart_qty', FALSE)) {
  605.     $form['base']['default_qty'] = array(
  606.       '#type' => 'uc_quantity',
  607.       '#title' => t('Default quantity to add to cart'),
  608.       '#default_value' => $node->default_qty,
  609.       '#description' => t('Use 0 to disable the quantity field next to the add to cart button.'),
  610.       '#weight' => 27,
  611.       '#allow_zero' => TRUE,
  612.     );
  613.   }
  614.   else {
  615.     $form['base']['default_qty'] = array(
  616.       '#type' => 'value',
  617.       '#value' => $node->default_qty,
  618.     );
  619.   }
  620.  
  621.   $form['base']['ordering'] = array(
  622.     '#type' => 'weight',
  623.     '#title' => t('List position'),
  624.     '#description' => t("Specify a value to set this product's position in product lists.<br />Products in the same position will be sorted alphabetically."),
  625.     '#delta' => 25,
  626.     '#default_value' => $node->ordering,
  627.     '#weight' => 30,
  628.   );
  629.  
  630.   return $form;
  631. }
  632.  
  633. /**
  634.  * Implements hook_view().
  635.  */
  636. function uc_product_kit_view($node, $view_mode) {
  637.   // Give modules a chance to alter this product.  If it is a variant, this
  638.   // will have been done already by uc_product_load_variant(), so we check a
  639.   // flag to be sure not to alter twice.
  640.   $variant = empty($node->variant) ? uc_product_load_variant($node->nid) : $node;
  641.  
  642.     if (module_exists('uc_cart') && empty($variant->data['display_only'])) {
  643.     $add_to_cart_form = drupal_get_form('uc_product_kit_add_to_cart_form_' . $variant->nid, clone $variant);
  644.     if (variable_get('uc_product_update_node_view', FALSE)) {
  645.       $variant = $add_to_cart_form['node']['#value'];
  646.     }
  647.   }
  648.  
  649.   // Calculate the display price.
  650.   $display_price = 0;
  651.   $suffixes = array();
  652.   if ($node->mutable != UC_PRODUCT_KIT_MUTABLE) {
  653.     // If this is a non-mutable kit, then sum the display price of each of the
  654.     // component products.
  655.     foreach ($variant->products as $product) {
  656.       $build = node_view($product, $view_mode);
  657.       $display_price += $build['display_price']['#value'] * $product->qty;
  658.       $suffixes += $build['display_price']['#suffixes'];
  659.     }
  660.   }
  661.   else {
  662.     // For mutable, just use the price.
  663.     $display_price = $variant->price;
  664.     $suffixes = array();
  665.   }
  666.  
  667.   $node->content['display_price'] = array(
  668.     '#theme' => 'uc_product_price',
  669.     '#value' => $display_price,
  670.     '#suffixes' => $suffixes,
  671.     '#attributes' => array(
  672.       'class' => array(
  673.         'product-kit',
  674.         'display-price',
  675.       ),
  676.     ),
  677.   );
  678.   $node->content['model'] = array(
  679.     '#theme' => 'uc_product_model',
  680.     '#model' => $variant->model,
  681.     '#view_mode' => $view_mode,
  682.   );
  683.   $node->content['list_price'] = array(
  684.     '#theme' => 'uc_product_price',
  685.     '#title' => t('List price:'),
  686.     '#value' => $variant->list_price,
  687.     '#attributes' => array(
  688.       'class' => array(
  689.         'product-kit',
  690.         'list-price',
  691.       ),
  692.     ),
  693.   );
  694.   $node->content['cost'] = array(
  695.     '#theme' => 'uc_product_price',
  696.     '#title' => t('Cost:'),
  697.     '#value' => $variant->cost,
  698.     '#attributes' => array(
  699.       'class' => array(
  700.         'product-kit',
  701.         'cost',
  702.       ),
  703.     ),
  704.     '#access' => user_access('administer product kits'),
  705.   );
  706.   $node->content['sell_price'] = array(
  707.     '#theme' => 'uc_product_price',
  708.     '#title' => t('Price:'),
  709.     '#value' => $variant->sell_price,
  710.     '#attributes' => array(
  711.       'class' => array(
  712.         'product-kit',
  713.         'sell-price',
  714.       ),
  715.     ),
  716.   );
  717.   $node->content['weight'] = array(
  718.     '#theme' => 'uc_product_weight',
  719.     '#amount' => $variant->weight,
  720.     '#units' => $variant->weight_units,
  721.     '#view_mode' => $view_mode,
  722.   );
  723.  
  724.   if ($node->mutable != UC_PRODUCT_KIT_UNMUTABLE_NO_LIST) {
  725.     $node->content['products'] = array('#weight' => 6);
  726.  
  727.     $i = 0;
  728.     foreach ($node->products as $product) {
  729.       $node->content['products'][$product->nid]['qty'] = array(
  730.         '#markup' => '<div class="product-qty">' . theme('uc_product_kit_list_item', array('product' => $product)) . '</div>',
  731.       );
  732.       $node->content['products'][$product->nid]['#weight'] = $i++;
  733.     }
  734.   }
  735.  
  736.   if (isset($add_to_cart_form)) {
  737.     $node->content['add_to_cart'] = array(
  738.       '#theme' => 'uc_product_kit_add_to_cart',
  739.       '#view_mode' => $view_mode,
  740.       '#form' => drupal_get_form('uc_product_kit_add_to_cart_form_' . $variant->nid, $variant),
  741.     );
  742.   }
  743.  
  744.   $node->content['#node'] = $variant;
  745.  
  746.   return $node;
  747. }
  748.  
  749. /**
  750.  * Lets the cart know how many of which products are included in a kit.
  751.  *
  752.  * uc_attribute_form_alter() hooks into this form to add attributes to each
  753.  * element in $form['products'].
  754.  *
  755.  * @see uc_product_kit_add_to_cart_form_submit()
  756.  * @ingroup forms
  757.  */
  758. function uc_product_kit_add_to_cart_form($form, &$form_state, $node) {
  759.   $form['nid'] = array('#type' => 'value', '#value' => $node->nid);
  760.   $form['products'] = array('#tree' => TRUE);
  761.   foreach ($node->products as $i => $product) {
  762.     $form['products'][$i] = array('#title' => check_plain($product->title));
  763.     $form['products'][$i]['nid'] = array('#type' => 'hidden', '#value' => $product->nid);
  764.     $form['products'][$i]['qty'] = array('#type' => 'hidden', '#value' => $product->qty);
  765.   }
  766.   if ($node->default_qty > 0 && variable_get('uc_product_add_to_cart_qty', FALSE)) {
  767.     $form['qty'] = array(
  768.       '#type' => 'uc_quantity',
  769.       '#title' => t('Quantity'),
  770.       '#default_value' => $node->default_qty,
  771.     );
  772.   }
  773.   else {
  774.     $form['qty'] = array('#type' => 'hidden', '#value' => $node->default_qty ? $node->default_qty : 1);
  775.   }
  776.  
  777.   $form['actions'] = array('#type' => 'actions');
  778.   $form['actions']['submit'] = array(
  779.     '#type' => 'submit',
  780.     '#value' => t('Add to cart'),
  781.     '#id' => 'edit-submit-' . $node->nid,
  782.     '#attributes' => array(
  783.       'class' => array('node-add-to-cart'),
  784.     ),
  785.   );
  786.  
  787.   $form['node'] = array(
  788.     '#type' => 'value',
  789.     '#value' => isset($form_state['storage']['variant']) ? $form_state['storage']['variant'] : $node,
  790.   );
  791.  
  792.   uc_form_alter($form, $form_state, __FUNCTION__);
  793.  
  794.   return $form;
  795. }
  796.  
  797. function uc_product_kit_add_to_cart_form_validate($form, &$form_state) {
  798.   uc_product_add_to_cart_form_validate($form, $form_state);
  799.   foreach ($form_state['storage']['variant']->products as &$product) {
  800.     $data = module_invoke_all('uc_add_to_cart_data', $form_state['values']['products'][$product->nid]);
  801.     $data += $product->data;
  802.     $qty = $product->qty;
  803.     $product = uc_product_load_variant($product->nid, $data);
  804.     $product->qty = $qty;
  805.   }
  806. }
  807.  
  808. /**
  809.  * Adds each product kit's component to the cart in the correct quantities.
  810.  *
  811.  * @see uc_product_kit_add_to_cart_form()
  812.  */
  813. function uc_product_kit_add_to_cart_form_submit($form, &$form_state) {
  814.   $node = node_load($form_state['values']['nid']);
  815.   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'))));
  816.   $form_state['redirect'] = uc_cart_add_item($form_state['values']['nid'], $form_state['values']['qty'], $form_state['values']);
  817. }
  818.  
  819. /**
  820.  * Add-to-cart button with any extra fields.
  821.  *
  822.  * @see uc_product_kit_buy_it_now_form_validate()
  823.  * @see uc_product_kit_buy_it_now_form_submit()
  824.  * @ingroup forms
  825.  */
  826. function uc_product_kit_buy_it_now_form($form, &$form_state, $node) {
  827.   $form['nid'] = array(
  828.     '#type' => 'hidden',
  829.     '#value' => $node->nid,
  830.     );
  831.   if ($node->type == 'product_kit') {
  832.     $form['products'] = array('#tree' => TRUE);
  833.     foreach ($node->products as $i => $product) {
  834.       $form['products'][$i] = array('#title' => check_plain($product->title));
  835.       $form['products'][$i]['nid'] = array('#type' => 'hidden', '#value' => $product->nid);
  836.       $form['products'][$i]['qty'] = array('#type' => 'hidden', '#value' => $product->qty);
  837.     }
  838.   }
  839.  
  840.   $form['actions'] = array('#type' => 'actions');
  841.   $form['actions']['submit'] = array(
  842.     '#type' => 'submit',
  843.     '#value' => t('Add to cart'),
  844.     '#id' => 'edit-submit-' . $node->nid,
  845.     '#attributes' => array(
  846.       'class' => array('list-add-to-cart'),
  847.     ),
  848.   );
  849.  
  850.   uc_form_alter($form, $form_state, __FUNCTION__);
  851.  
  852.   return $form;
  853. }
  854.  
  855. /**
  856.  * Redirects to the product kit page so attributes may be selected.
  857.  *
  858.  * @see uc_product_kit_buy_it_now_form()
  859.  */
  860. function uc_product_kit_buy_it_now_form_validate($form, &$form_state) {
  861.   if (module_exists('uc_attribute')) {
  862.     $node = node_load($form_state['values']['nid']);
  863.     if (is_array($node->products)) {
  864.       foreach ($node->products as $nid => $product) {
  865.         $attributes = uc_product_get_attributes($nid);
  866.         if (!empty($attributes)) {
  867.           drupal_set_message(t('This product has options that need to be selected before purchase. Please select them in the form below.'), 'error');
  868.           drupal_goto('node/' . $form_state['values']['nid']);
  869.         }
  870.       }
  871.     }
  872.   }
  873. }
  874.  
  875. /**
  876.  * Form submission handler for uc_product_kit_buy_it_now_form().
  877.  *
  878.  * @see uc_product_kit_buy_it_now_form()
  879.  */
  880. function uc_product_kit_buy_it_now_form_submit($form, &$form_state) {
  881.   $node = node_load($form_state['values']['nid']);
  882.   if (module_exists('uc_attribute')) {
  883.     $attributes = uc_product_get_attributes($node->nid);
  884.     if (!empty($attributes)) {
  885.       drupal_set_message(t('This product has options that need to be selected before purchase. Please select them in the form below.'), 'error');
  886.       $form_state['redirect'] = drupal_get_path_alias('node/' . $form_state['values']['nid']);
  887.       return;
  888.     }
  889.     if (is_array($node->products)) {
  890.       foreach ($node->products as $nid => $product) {
  891.         $attributes = uc_product_get_attributes($nid);
  892.         if (!empty($attributes)) {
  893.           drupal_set_message(t('This product has options that need to be selected before purchase. Please select them in the form below.'), 'error');
  894.           $form_state['redirect'] = drupal_get_path_alias('node/' . $form_state['values']['nid']);
  895.           return;
  896.         }
  897.       }
  898.     }
  899.   }
  900.   $form_state['redirect'] = uc_cart_add_item($form_state['values']['nid'], 1, $form_state['values'], NULL, variable_get('uc_cart_add_item_msg', TRUE));
  901. }
  902.  
  903. /**
  904.  * Implements hook_uc_product_types().
  905.  */
  906. function uc_product_kit_uc_product_types() {
  907.   return array('product_kit');
  908. }
  909.  
  910. /**
  911.  * Implements hook_uc_store_status().
  912.  */
  913. function uc_product_kit_uc_store_status() {
  914.   if (module_exists('filefield')) {
  915.     module_load_include('inc', 'content', 'includes/content.crud');
  916.     // Check for filefields on products.
  917.     if ($field = variable_get('uc_image_product_kit', '')) {
  918.       $instances = content_field_instance_read(array('field_name' => $field, 'type_name' => 'product_kit'));
  919.     }
  920.     else {
  921.       $instances = array();
  922.     }
  923.  
  924.     if (!count($instances)) {
  925.       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')))));
  926.     }
  927.   }
  928.  
  929. }
  930.  
  931. /**
  932.  * Implements hook_uc_add_to_cart().
  933.  */
  934. function uc_product_kit_uc_add_to_cart($nid, $qty, $kit_data) {
  935.   $node = node_load($nid);
  936.   if ($node->type == 'product_kit') {
  937.     $cart = uc_cart_get_contents();
  938.     $unique = uniqid('', TRUE);
  939.     $update = array();
  940.     $product_data = array();
  941.  
  942.     foreach ($node->products as $product) {
  943.       $data = array('kit_id' => $node->nid, 'module' => 'uc_product_kit') + module_invoke_all('uc_add_to_cart_data', $kit_data['products'][$product->nid]);
  944.       $product_data[$product->nid] = $data;
  945.       foreach ($cart as $item) {
  946.         if ($item->nid == $product->nid && isset($item->data['kit_id']) && $item->data['kit_id'] == $node->nid) {
  947.           // There is something in the cart like the product kit. Update
  948.           // by default, but check that it's possible.
  949.           $data['unique_id'] = $item->data['unique_id'];
  950.           if ($item->data == $data) {
  951.             // This product is a candidate for updating the cart quantity.
  952.             // Make sure the data arrays will compare as equal when serialized.
  953.             $product_data[$product->nid] = $item->data;
  954.             $update[$product->nid] = TRUE;
  955.           }
  956.         }
  957.       }
  958.  
  959.     }
  960.  
  961.     // The product kit can update its items only if they all can be updated.
  962.     if (count($update) != count($node->products)) {
  963.       foreach ($node->products as $product) {
  964.         $data = $product_data[$product->nid];
  965.         $data['unique_id'] = $unique;
  966.         uc_cart_add_item($product->nid, $product->qty * $qty, $data, NULL, FALSE, FALSE, FALSE);
  967.       }
  968.     }
  969.     else {
  970.       foreach ($node->products as $product) {
  971.         $data = $product_data[$product->nid];
  972.         uc_cart_add_item($product->nid, $product->qty * $qty, $data, NULL, FALSE, FALSE, FALSE);
  973.       }
  974.     }
  975.  
  976.     // Rebuild the cart items cache.
  977.     uc_cart_get_contents(NULL, 'rebuild');
  978.  
  979.     return array(array('success' => FALSE, 'silent' => TRUE, 'message' => ''));
  980.   }
  981. }
  982.  
  983. /**
  984.  * Implements hook_uc_product_alter().
  985.  */
  986. function uc_product_kit_uc_product_alter(&$variant) {
  987.   if (isset($variant->data['kit_id'])) {
  988.     // If this is a kit component load, we would cause infinite recursion trying
  989.     // to node_load() the parent, but we already have the discount available.
  990.     if (isset($variant->data['kit_discount'])) {
  991.       $discount = $variant->data['kit_discount'];
  992.     }
  993.     elseif (($kit = node_load($variant->data['kit_id'])) && $kit->mutable != UC_PRODUCT_KIT_MUTABLE) {
  994.       $discount = $kit->products[$variant->nid]->discount;
  995.     }
  996.     else {
  997.       $discount = 0;
  998.     }
  999.     $variant->price += $discount;
  1000.     $variant->data['module'] = 'uc_product_kit';
  1001.   }
  1002. }
  1003.  
  1004. /**
  1005.  * Implements hook_uc_order_product_alter().
  1006.  *
  1007.  * The hookups for making product kits work on the order edit admin screen.
  1008.  *
  1009.  * @param $product
  1010.  *   The order product being saved.
  1011.  * @param $order
  1012.  *   The order being edited.
  1013.  */
  1014. function uc_product_kit_uc_order_product_alter(&$product, $order) {
  1015.   if (empty($product->type) || $product->type !== 'product_kit') {
  1016.     return;
  1017.   }
  1018.  
  1019.   // Have to save each individual product if this is a kit.
  1020.   foreach ($product->products as $kit_product) {
  1021.     $qty = $kit_product->qty * $product->qty;
  1022.     $data = isset($kit_product->data) ? $kit_product->data : array();
  1023.     $data += module_invoke_all('uc_add_to_cart_data', $_POST['product_controls']['sub_products'][$kit_product->nid]);
  1024.     $data['shippable'] = $product->shippable;
  1025.     $kit_product = uc_product_load_variant($kit_product->nid, $data);
  1026.     $kit_product->qty = $qty;
  1027.     drupal_alter('uc_order_product', $kit_product, $order);
  1028.  
  1029.     // Save the individual item to the order.
  1030.     uc_order_product_save($order->order_id, $kit_product);
  1031.   }
  1032.  
  1033.   // Don't save the base kit node, though.
  1034.   $product->skip_save = TRUE;
  1035. }
  1036.  
  1037. /**
  1038.  * Implements hook_uc_cart_display().
  1039.  *
  1040.  * Displays either the kit as a whole, or each individual product based on the
  1041.  *       store configuration. Each product in the cart that was added by
  1042.  * uc_product_kit was also given a unique kit id in order to help prevent
  1043.  * collisions. The side effect is that identical product kits are listed
  1044.  * separately if added separately. The customer may still change the quantity
  1045.  * of kits like other products.
  1046.  *
  1047.  * @param $item
  1048.  *   An item in the shopping cart.
  1049.  *
  1050.  * @return
  1051.  *   A form element array to be processed by uc_cart_view_form().
  1052.  */
  1053. function uc_product_kit_uc_cart_display($item) {
  1054.   static $elements = array();
  1055.   static $products;
  1056.   $unique_id = $item->data['unique_id'];
  1057.   $kit = node_load($item->data['kit_id']);
  1058.   if ($kit->mutable == UC_PRODUCT_KIT_MUTABLE) {
  1059.     return uc_product_uc_cart_display($item);
  1060.   }
  1061.   else {
  1062.     if (!isset($products[$unique_id])) {
  1063.       // Initialize table row.
  1064.       $kit_qty = $item->qty / $kit->products[$item->nid]->qty;
  1065.       $element = array();
  1066.       $element['nid'] = array('#type' => 'value', '#value' => $kit->nid);
  1067.       $element['module'] = array('#type' => 'value', '#value' => 'uc_product_kit');
  1068.       $element['remove'] = array('#type' => 'submit', '#value' => t('Remove'));
  1069.       $element['title'] = array('#markup' => l($kit->title, 'node/' . $kit->nid));
  1070.       $element['qty'] = array(
  1071.         '#type' => 'textfield',
  1072.         '#title' => t('Quantity'),
  1073.         '#title_display' => 'invisible',
  1074.         '#default_value' => $kit_qty,
  1075.         '#size' => 5,
  1076.         '#maxlength' => 6,
  1077.       );
  1078.       $element['description'] = array('#markup' => '');
  1079.       $element['#total'] = 0;
  1080.       $element['#suffixes'] = array();
  1081.       $element['#extra'] = array();
  1082.       $element['#entity'] = $kit; // Override the entity associated with this
  1083.                                   // render-array to be the kit itself.
  1084.  
  1085.       $elements[$unique_id] = $element;
  1086.     }
  1087.     // Add product specific information.
  1088.     $extra = uc_product_get_description($item);
  1089.  
  1090.     if ($kit->mutable == UC_PRODUCT_KIT_UNMUTABLE_WITH_LIST) {
  1091.       $elements[$unique_id]['#extra'][] = array(
  1092.         'data' => theme('uc_product_kit_list_item', array('product' => $item)) . $extra,
  1093.         'class' => array('kit-component-cart-desc'),
  1094.       );
  1095.     }
  1096.  
  1097.     $build = node_view($item);
  1098.     $elements[$unique_id]['#total'] += $build['display_price']['#value'] * $item->qty;
  1099.     $elements[$unique_id]['#suffixes'] += $build['display_price']['#suffixes'];
  1100.     $elements[$unique_id]['data'][$item->nid] = $item;
  1101.     $products[$unique_id][] = $item->nid;
  1102.  
  1103.     // Check if all products in this kit have been accounted for.
  1104.     $done = TRUE;
  1105.     foreach ($kit->products as $product) {
  1106.       if (!in_array($product->nid, $products[$unique_id])) {
  1107.         $done = FALSE;
  1108.         break;
  1109.       }
  1110.     }
  1111.  
  1112.     if ($done) {
  1113.       drupal_add_css(drupal_get_path('module', 'uc_product_kit') . '/uc_product_kit.css');
  1114.       $elements[$unique_id]['data'] = array('#type' => 'value', '#value' => serialize($elements[$unique_id]['data']));
  1115.       if ($kit->mutable == UC_PRODUCT_KIT_UNMUTABLE_WITH_LIST) {
  1116.         $elements[$unique_id]['description']['#markup'] .= theme('item_list', array('items' => $elements[$unique_id]['#extra'], 'attributes' => array('class' => array('product-description'))));
  1117.       }
  1118.       $element = $elements[$unique_id];
  1119.       unset($products[$unique_id]);
  1120.       unset($elements[$unique_id]);
  1121.       return $element;
  1122.     }
  1123.   }
  1124.   return array();
  1125. }
  1126.  
  1127. /**
  1128.  * Implements hook_uc_update_cart_item().
  1129.  *
  1130.  * Handles individual products or entire kits.
  1131.  */
  1132. function uc_product_kit_uc_update_cart_item($nid, $data = array(), $qty, $cid = NULL) {
  1133.   if (!$nid) return NULL;
  1134.   $cid = !(is_null($cid) || empty($cid)) ? $cid : uc_cart_get_id();
  1135.   if ($qty < 1) {
  1136.     foreach ($data as $p_nid => $product) {
  1137.       uc_cart_remove_item($p_nid, $cid, $product->data);
  1138.     }
  1139.   }
  1140.   else {
  1141.     if (isset($data['kit_id'])) {
  1142.       // Product was listed individually.
  1143.       uc_product_uc_update_cart_item($nid, $data, $qty, $cid);
  1144.     }
  1145.     else {
  1146.       $kit = node_load($nid);
  1147.       foreach ($data as $p_nid => $product) {
  1148.         uc_product_uc_update_cart_item($p_nid, $product->data, $qty * $kit->products[$p_nid]->qty, $cid);
  1149.       }
  1150.     }
  1151.   }
  1152. }
  1153.  
  1154. /**
  1155.  * usort() callback.
  1156.  */
  1157. function _uc_product_kit_sort_products($a, $b) {
  1158.   if ($a->ordering == $b->ordering) {
  1159.     return strcmp($a->title, $b->title);
  1160.   }
  1161.   else {
  1162.     return ($a->ordering < $b->ordering) ? -1 : 1;
  1163.   }
  1164. }
  1165.  
  1166. /**
  1167.  * Implements hook_views_api().
  1168.  */
  1169. function uc_product_kit_views_api() {
  1170.   return array(
  1171.     'api' => 3,
  1172.     'path' => drupal_get_path('module', 'uc_product_kit') . '/views',
  1173.   );
  1174. }
Advertisement
Add Comment
Please, Sign In to add comment