You are here

uc_product.module in Ubercart 5

The product module for Ubercart.

Provides information that is common to all products, and user-defined product classes for more specification. Recommends the image and taxonomy modules.

Coded by: Lyle Mantooth Product Features by: Ryan Szrama

File

uc_product/uc_product.module
View source
<?php

/**
 * @file
 * The product module for Ubercart.
 *
 * Provides information that is common to all products, and user-defined product
 * classes for more specification. Recommends the image and taxonomy modules.
 *
 * Coded by: Lyle Mantooth
 * Product Features by: Ryan Szrama
 */

/******************************************************************************
 * Drupal Hooks                                                               *
 ******************************************************************************/

/**
 * Implementation of hook_menu().
 */
function uc_product_menu($may_cache) {
  $items = array();
  if ($may_cache) {
    $items[] = array(
      'path' => 'products',
      'title' => t('Products'),
      'description' => t('List all published product nodes.'),
      'access' => user_access('access content'),
      'callback' => 'uc_product_default',
      'type' => MENU_SUGGESTED_ITEM,
    );
    $items[] = array(
      'path' => 'admin/store/products',
      'title' => t('Products'),
      'description' => t('Administer products, classes, and more.'),
      'access' => user_access('administer products'),
      'callback' => 'uc_product_administration',
      'type' => MENU_NORMAL_ITEM,
      'weight' => -2,
    );
    $items[] = array(
      'path' => 'admin/store/products/view',
      'title' => t('View products'),
      'description' => t('Build and view a list of product nodes.'),
      'access' => user_access('administer products'),
      'type' => MENU_NORMAL_ITEM,
      'weight' => -10,
    );
    $items[] = array(
      'path' => 'admin/store/products/classes',
      'title' => t('Manage classes'),
      'description' => t('Create and edit product node types.'),
      'access' => user_access('administer product classes'),
      'callback' => 'uc_product_class_default',
      'type' => MENU_NORMAL_ITEM,
      'weight' => -2,
    );
    $items[] = array(
      'path' => 'admin/store/settings/products',
      'title' => t('Product settings'),
      'description' => t('Configure product settings.'),
      'access' => user_access('administer products'),
      'callback' => 'uc_product_settings_overview',
      'type' => MENU_NORMAL_ITEM,
    );
    $items[] = array(
      'path' => 'admin/store/settings/products/overview',
      'title' => t('Overview'),
      'weight' => -10,
      'type' => MENU_DEFAULT_LOCAL_TASK,
    );
    $items[] = array(
      'path' => 'admin/store/settings/products/edit',
      'title' => t('Edit'),
      'access' => user_access('administer products'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array(
        'uc_product_settings_form',
      ),
      'weight' => -5,
      'type' => MENU_LOCAL_TASK,
    );
    $items[] = array(
      'path' => 'admin/store/settings/products/edit/general',
      'title' => t('Product settings'),
      'access' => user_access('administer products'),
      'weight' => -10,
      'type' => MENU_DEFAULT_LOCAL_TASK,
    );
    $items[] = array(
      'path' => 'admin/store/settings/products/edit/fields',
      'title' => t('Product fields'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array(
        'uc_product_field_settings_form',
      ),
      'access' => user_access('administer products'),
      'weight' => -5,
      'type' => MENU_LOCAL_TASK,
    );
    $items[] = array(
      'path' => 'admin/store/settings/products/edit/features',
      'title' => t('Product features'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array(
        'uc_product_feature_settings_form',
      ),
      'access' => user_access('administer product features'),
      'weight' => 0,
      'type' => MENU_LOCAL_TASK,
    );
  }
  else {

    // Insert subitems into the edit node page for product types.
    if (arg(0) == 'node' && is_numeric(arg(1)) && arg(2) == 'edit') {
      $node = node_load(arg(1));
      if ($node->nid && in_array($node->type, module_invoke_all('product_types'))) {
        $items[] = array(
          'path' => 'node/' . arg(1) . '/edit/product',
          'title' => t('Product'),
          'weight' => -10,
          'type' => MENU_DEFAULT_LOCAL_TASK,
        );
        $features = module_invoke_all('product_feature');
        if (!empty($features)) {
          $items[] = array(
            'path' => 'node/' . arg(1) . '/edit/features',
            'title' => t('Features'),
            'callback' => 'uc_product_features',
            'callback arguments' => array(
              $node,
            ),
            'access' => user_access('administer product features'),
            'weight' => 10,
            'type' => MENU_LOCAL_TASK,
          );
        }
      }
    }
    $items[] = array(
      'path' => 'admin/store/settings/products/defaults/' . arg(5),
      'title' => t('Imagecache default settings'),
      'access' => user_access('administer products'),
      'callback' => 'uc_product_image_defaults',
      'callback arguments' => array(
        arg(5),
      ),
      'type' => MENU_CALLBACK,
    );
    $items[] = array(
      'path' => 'admin/store/products/classes/' . arg(4),
      'title' => t('Product class'),
      'access' => user_access('administer product classes'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array(
        'uc_product_class_form',
        arg(4),
      ),
      'type' => MENU_CALLBACK,
    );
    $items[] = array(
      'path' => 'admin/store/products/classes/' . arg(4) . '/edit',
      'title' => t('Edit'),
      'type' => MENU_DEFAULT_LOCAL_TASK,
      'weight' => -5,
    );
    $items[] = array(
      'path' => 'admin/store/products/classes/' . arg(4) . '/delete',
      'access' => user_access('administer product classes'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array(
        'uc_product_class_delete_confirm',
        arg(4),
      ),
      'type' => MENU_CALLBACK,
    );
    $items[] = array(
      'path' => 'products/field_image_cache/' . arg(2),
      'access' => true,
      'callback' => '_uc_product_get_image_field_filepath',
      'callback arguments' => array(
        arg(2),
      ),
      'type' => MENU_CALLBACK,
    );
    drupal_add_css(drupal_get_path('module', 'uc_product') . '/uc_product.css');
  }
  return $items;
}

/**
 * Implementation of hook_help().
 */
function uc_product_help($section = '') {

  // Do things here later. Figure out what you need to say for each section.
  switch ($section) {
    case 'admin/settings/module#description':
      $output = t('Create products for sale in an online store.');
      break;
  }
  return $output;
}

/**
 * Implementation of hook_perm().
 */
function uc_product_perm() {
  $perms = array(
    'administer products',
    'administer product classes',
    'administer product features',
  );
  foreach (node_get_types() as $type) {
    if ($type->module == 'uc_product') {
      $name = check_plain($type->type);
      if ($name == 'product') {
        $name = '';
      }
      else {
        $name .= ' ';
      }
      $perms[] = 'create ' . $name . 'products';
      $perms[] = 'edit own ' . $name . 'products';
      $perms[] = 'edit ' . $name . 'products';
    }
  }
  return $perms;
}

/**
 * Implementation of hook_access().
 */
function uc_product_access($op, $node) {
  global $user;
  $type = is_string($node) ? $node : (is_array($node) ? $node['type'] : $node->type);
  if ($type == 'product') {
    $type = '';
  }
  else {
    $type .= ' ';
  }
  switch ($op) {
    case 'create':
      return user_access('create ' . $type . 'products');
    case 'update':
    case 'delete':
      if (user_access('edit ' . $type . 'products') || user_access('edit own ' . $type . 'products') && $user->uid == $node->uid) {
        return TRUE;
      }
  }
}

/**
 * Implementation of hook_enable().
 *
 * Set up default imagefield and imagecache settings.
 */
function uc_product_enable() {
  $node_types = node_get_types('types');
  $product_classes = array(
    'product',
  );
  $result = db_query("SELECT pcid, name, description FROM {uc_product_classes}");
  while ($product_class = db_fetch_object($result)) {
    $product_classes[] = $product_class->pcid;
  }
  foreach ($node_types as $type => $info) {
    if ($info->module == 'node' && in_array($type, $product_classes)) {
      $info->module = 'uc_product';
      $info->custom = 0;
      node_type_save($info);
    }
  }
  if (module_exists('imagefield')) {
    $result = db_query("SELECT field_name FROM {node_field} WHERE field_name = 'field_image_cache' AND type = 'image'");
    if (!db_num_rows($result)) {
      db_query("INSERT INTO {node_field} (field_name, type, global_settings, required, multiple, db_storage) VALUES ('field_image_cache', 'image', '%s', 0, 1, 0)", 'a:0:{}');
    }
    $image_path = file_create_path();
    $widget_settings = array(
      'max_resolution' => '0',
      'image_path' => $image_path,
      'custom_alt' => 1,
      'custom_title' => 1,
      'teaser_preset' => null,
      'body_preset' => null,
    );
    $display_settings = array(
      'label' => array(
        'format' => 'hidden',
      ),
      'teaser' => array(
        'format' => 'hidden',
      ),
      'full' => array(
        'format' => 'hidden',
      ),
    );
    foreach (module_invoke_all('product_types') as $type) {
      $result = db_query("SELECT * FROM {node_field_instance} WHERE field_name = 'field_image_cache' and type_name = '%s'", $type);
      if (!db_num_rows($result)) {
        db_query("INSERT INTO {node_field_instance} VALUES ('field_image_cache', '%s', -2, 'Image', 'image', '%s', '%s', '')", $type, serialize($widget_settings), serialize($display_settings));
      }
      switch ($GLOBALS['db_type']) {
        case 'mysql':
        case 'mysqli':
          db_query("CREATE TABLE IF NOT EXISTS {content_field_image_cache} (\n            `vid` int(10) unsigned NOT NULL default '0',\n            `delta` int(10) unsigned NOT NULL default '0',\n            `nid` int(10) unsigned NOT NULL default '0',\n            `field_image_cache_fid` int(11) NOT NULL default '0',\n            `field_image_cache_title` varchar(255) NOT NULL default '',\n            `field_image_cache_alt` varchar(255) NOT NULL default '',\n            PRIMARY KEY  (`vid`,`delta`)\n          ) /*!40100 DEFAULT CHARACTER SET utf8 */;");
          break;
        case 'pgsql':
          $result = db_query("SELECT relname FROM pg_class WHERE relname = '{content_field_image_cache}'");
          if (!db_num_rows($result)) {
            db_query('CREATE TABLE {content_field_image_cache} (
              "vid" int_unsigned NOT NULL default \'0\',
              "delta" int_unsigned NOT NULL default \'0\',
              "nid" int_unsigned NOT NULL default \'0\',
              "field_image_cache_fid" integer NOT NULL default \'0\',
              "field_image_cache_title" varchar(255) NOT NULL,
              "field_image_cache_alt" varchar(255) NOT NULL,
              PRIMARY KEY  ("vid","delta")
            );');
          }
          break;
      }
    }
    content_clear_type_cache();
  }
  if (module_exists('imagecache')) {
    $presets = array(
      'product' => 0,
      'product_list' => 0,
      'uc_thumbnail' => 0,
    );
    $result = db_query("SELECT * FROM {imagecache_preset} WHERE presetname IN ('" . implode("','", array_keys($presets)) . "')");
    while ($preset = db_fetch_array($result)) {
      $presets[$preset['presetname']] = $preset['presetid'];
    }

    //drupal_set_message('<pre>'. print_r($presets, true) .'</pre>');
    foreach ($presets as $name => $id) {
      if ($id == 0) {
        $id = db_next_id('{imagecache_preset}_presetid');
        db_query("INSERT INTO {imagecache_preset} (presetid, presetname) VALUES (%d, '%s')", $id, $name);
      }
    }
    $result = db_query("SELECT ia.actionid, ip.presetid, ip.presetname FROM {imagecache_preset} AS ip LEFT JOIN {imagecache_action} AS ia ON ip.presetid = ia.presetid WHERE ip.presetname IN ('" . implode("','", array_keys($presets)) . "')");
    $presets = array();
    while ($preset = db_fetch_array($result)) {
      if (is_null($preset['actionid'])) {
        switch ($preset['presetname']) {
          case 'product':
            db_query("INSERT INTO {imagecache_action} (actionid, presetid, weight, data) VALUES (%d, %d, 0, '%s')", db_next_id('{imagecache_action}_actionid'), $preset['presetid'], 'a:4:{s:8:"function";s:5:"scale";s:3:"fit";s:6:"inside";s:5:"width";s:3:"100";s:6:"height";s:3:"100";}');
            break;
          case 'product_list':
            db_query("INSERT INTO {imagecache_action} (actionid, presetid, weight, data) VALUES (%d, %d, 0, '%s')", db_next_id('{imagecache_action}_actionid'), $preset['presetid'], 'a:4:{s:8:"function";s:5:"scale";s:3:"fit";s:6:"inside";s:5:"width";s:3:"100";s:6:"height";s:3:"100";}');
            break;
          case 'uc_thumbnail':
            db_query("INSERT INTO {imagecache_action} (actionid, presetid, weight, data) VALUES (%d, %d, 0, '%s')", db_next_id('{imagecache_action}_actionid'), $preset['presetid'], 'a:4:{s:8:"function";s:5:"scale";s:3:"fit";s:6:"inside";s:5:"width";s:2:"35";s:6:"height";s:2:"35";}');
            break;
        }
      }
    }
    cache_clear_all('imagecache:presets', 'cache');
  }
}
function uc_product_disable() {
  $product_types = node_get_types('types');

  // node_type_rebuild() deletes disabled modules' node types. Give these
  // node types to node.module to prevent this. Get them back during
  // hook_enable().
  foreach ($product_types as $type) {
    if ($type->module == 'uc_product') {
      $type->module = 'node';
      $type->custom = 1;
      node_type_save($type);
    }
  }
}

/**
 * Implementation of hook_node_info().
 *
 * Create node types for each product class and other product modules.
 */
function uc_product_node_info($reset = false) {
  static $types = array();
  $title_label = t('Name');
  $body_label = t('Description');
  if (empty($types) || $reset) {
    $types = array();
    $types['product'] = array(
      'name' => t('Product'),
      'module' => 'uc_product',
      'description' => t('This node displays the representation of a product for sale on the website. It includes all the unique information that can be attributed to a specific model number.'),
      'title_label' => $title_label,
      'body_label' => $body_label,
    );
    $result = db_query("SELECT pcid, name, description FROM {uc_product_classes}");
    while ($class = db_fetch_object($result)) {
      $types[$class->pcid] = array(
        'name' => $class->name,
        'module' => 'uc_product',
        'description' => $class->description,
        'title_label' => $title_label,
        'body_label' => $body_label,
      );
    }
  }
  return $types;
}

/**
 * Implementation of hook_forms().
 *
 * Register an "add to cart" form for each product to prevent id collisions.
 */
function uc_product_forms($saved_args) {
  $forms = array();
  if (substr($saved_args[0], 0, 27) == 'uc_product_add_to_cart_form' || substr($saved_args[0], 0, 26) == 'uc_catalog_buy_it_now_form') {
    $product = $saved_args[1];
    if (in_array($product->type, array_keys(uc_product_node_info()))) {
      $forms['uc_product_add_to_cart_form_' . $product->nid] = array(
        'callback' => 'uc_product_add_to_cart_form',
      );
      $forms['uc_catalog_buy_it_now_form_' . $product->nid] = array(
        'callback' => 'uc_catalog_buy_it_now_form',
      );
    }
  }
  return $forms;
}

/**
 * Implementation of hook_form().
 *
 * @ingroup forms
 * @see theme_uc_product_form_prices
 * @see theme_uc_product_form_weight
 * @see theme_uc_product_dimensions
 * @see uc_product_form_validate
 */
function uc_product_form(&$node) {
  $type = node_get_types('type', $node);
  $location = array();
  $location[] = menu_get_item(null, 'admin');
  $location[] = menu_get_item(null, 'admin/store');
  $location[] = menu_get_item(null, 'admin/store/products');
  $location[] = menu_get_item(null, 'admin/store/settings/products');
  $breadcrumb = array();
  foreach ($location as $item) {
    $breadcrumb[] = l($item['title'], $item['path']);
  }
  drupal_set_breadcrumb($breadcrumb);
  $sign_flag = variable_get('uc_sign_after_amount', FALSE);
  $currency_sign = variable_get('uc_currency_sign', '$');
  $form['title'] = array(
    '#type' => 'textfield',
    '#title' => $type->title_label,
    '#required' => TRUE,
    '#weight' => -5,
    '#default_value' => $node->title,
    '#description' => t('Name of the product.'),
  );
  if ($type->has_body) {
    $form['body_filter']['body'] = array(
      '#type' => 'textarea',
      '#title' => $type->body_label,
      '#required' => FALSE,
      '#default_value' => $node->body,
      '#rows' => 20,
      '#description' => t('Enter the product description used for product teasers and pages.'),
    );
    $form['body_filter']['format'] = filter_form($node->format);
    $form['body_filter']['#weight'] = -4;
  }
  $form['base'] = array(
    '#type' => 'fieldset',
    '#title' => t('Product information'),
    '#collapsible' => true,
    '#collapsed' => false,
    '#weight' => -1,
    '#attributes' => array(
      'class' => 'product-field',
    ),
  );
  $form['base']['model'] = array(
    '#type' => 'textfield',
    '#title' => t('SKU'),
    '#required' => TRUE,
    '#default_value' => $node->model,
    '#description' => t('Product SKU/model.'),
    '#weight' => 0,
    '#size' => 32,
  );
  $form['base']['prices'] = array(
    '#weight' => 5,
    '#theme' => 'uc_product_form_prices',
  );
  $form['base']['prices']['list_price'] = array(
    '#type' => 'textfield',
    '#title' => t('List price'),
    '#required' => FALSE,
    '#default_value' => $node->list_price,
    '#description' => t('The listed MSRP.'),
    '#weight' => 0,
    '#size' => 20,
    '#maxlength' => 35,
    '#field_prefix' => $sign_flag ? '' : $currency_sign,
    '#field_suffix' => $sign_flag ? $currency_sign : '',
  );
  $form['base']['prices']['cost'] = array(
    '#type' => 'textfield',
    '#title' => t('Cost'),
    '#required' => FALSE,
    '#default_value' => $node->cost,
    '#description' => t("Your store's cost."),
    '#weight' => 1,
    '#size' => 20,
    '#maxlength' => 35,
    '#field_prefix' => $sign_flag ? '' : $currency_sign,
    '#field_suffix' => $sign_flag ? $currency_sign : '',
  );
  $form['base']['prices']['sell_price'] = array(
    '#type' => 'textfield',
    '#title' => t('Sell price'),
    '#required' => TRUE,
    '#default_value' => $node->sell_price,
    '#description' => t('Customer purchase price.'),
    '#weight' => 2,
    '#size' => 20,
    '#maxlength' => 35,
    '#field_prefix' => $sign_flag ? '' : $currency_sign,
    '#field_suffix' => $sign_flag ? $currency_sign : '',
  );
  $form['base']['shippable'] = array(
    '#type' => 'checkbox',
    '#title' => t('Product and its derivatives are shippable.'),
    '#default_value' => isset($node->shippable) ? $node->shippable : TRUE,
    '#weight' => 10,
  );
  $form['base']['weight'] = array(
    '#weight' => 15,
    '#theme' => 'uc_product_form_weight',
  );
  $form['base']['weight']['weight'] = array(
    '#type' => 'textfield',
    '#title' => t('Weight'),
    '#default_value' => $node->weight,
    '#size' => 10,
    '#maxlength' => 15,
  );
  $units = array(
    'lb' => t('Pounds'),
    'kg' => t('Kilograms'),
    'oz' => t('Ounces'),
    'g' => t('Grams'),
  );
  $form['base']['weight']['weight_units'] = array(
    '#type' => 'select',
    '#title' => t('Unit of measurement'),
    '#default_value' => $node->weight_units ? $node->weight_units : variable_get('uc_weight_unit', 'lb'),
    '#options' => $units,
  );
  $form['base']['dimensions'] = array(
    '#type' => 'fieldset',
    '#title' => t('Dimensions'),
    '#description' => t('Physical dimensions of the packaged product.'),
    '#weight' => 20,
    '#theme' => 'uc_product_dimensions_form',
  );
  $form['base']['dimensions']['length_units'] = array(
    '#type' => 'select',
    '#title' => t('Units of measurement'),
    '#options' => array(
      'in' => t('Inches'),
      'ft' => t('Feet'),
      'cm' => t('Centimeters'),
      'mm' => t('Millimeters'),
    ),
    '#default_value' => $node->length_units ? $node->length_units : variable_get('uc_length_unit', 'in'),
  );
  $form['base']['dimensions']['length'] = array(
    '#type' => 'textfield',
    '#title' => t('Length'),
    '#default_value' => $node->length,
    '#size' => 10,
  );
  $form['base']['dimensions']['width'] = array(
    '#type' => 'textfield',
    '#title' => t('Width'),
    '#default_value' => $node->width,
    '#size' => 10,
  );
  $form['base']['dimensions']['height'] = array(
    '#type' => 'textfield',
    '#title' => t('Height'),
    '#default_value' => $node->height,
    '#size' => 10,
  );
  $form['base']['pkg_qty'] = array(
    '#type' => 'textfield',
    '#title' => t('Package quantity'),
    '#default_value' => $node->pkg_qty ? $node->pkg_qty : 1,
    '#description' => t('For a package containing only this product, how many are in it?'),
    '#weight' => 25,
  );
  $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'),
    '#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,
  );
  return $form;
}

/**
 * @ingroup themeable
 */
function theme_uc_product_form_prices($prices) {
  return '<table><tr><td>' . "\n" . drupal_render($prices['list_price']) . '</td><td>' . drupal_render($prices['cost']) . '</td><td>' . drupal_render($prices['sell_price']) . "</td></tr></table>\n";
}

/**
 * @ingroup themeable
 */
function theme_uc_product_form_weight($form) {
  return '<table><tr><td>' . drupal_render($form['weight']) . '</td><td>' . drupal_render($form['weight_units']) . '</td></tr></table>';
}

/**
 * Put length, width, and height fields on the same line.
 *
 * @ingroup themeable
 */
function theme_uc_product_dimensions_form($form) {
  $output = '';
  $row = array();
  foreach (element_children($form) as $dimension) {
    $row[] = drupal_render($form[$dimension]);
  }
  $output .= theme('table', array(), array(
    $row,
  ));
  return $output;
}
function uc_product_validate($node) {
  $pattern = '/^\\d*(\\.\\d*)?$/';
  $price_error = t('Price must be in a valid number format. No commas and only one decimal point.');
  if (!empty($node->list_price) && !is_numeric($node->list_price) && !preg_match($pattern, $node->list_price)) {
    form_set_error('list_price', $price_error);
  }
  if (!empty($node->cost) && !is_numeric($node->cost) && !preg_match($pattern, $node->cost)) {
    form_set_error('cost', $price_error);
  }
  if (!is_numeric($node->sell_price) && !preg_match($pattern, $node->sell_price)) {
    form_set_error('sell_price', $price_error);
  }
  if (!empty($node->weight) && !is_numeric($node->weight)) {
    form_set_error('weight', t('Weight must be in a valid number format. No commas and only one decimal point.'));
  }
  if ($node->default_qty) {
    if (!is_numeric($node->default_qty)) {
      form_set_error('default_qty', t('Quantities should be numeric.'));
    }
    else {
      if ($node->default_qty < 0) {
        form_set_error('default_qty', t("Adding negative items to the cart doesn't make sense, so don't make it easy."));
      }
    }
  }
}

/**
 * Implementation of hook_insert().
 */
function uc_product_insert($node) {
  if (!isset($node->unique_hash)) {
    $node->unique_hash = md5($node->vid . $node->nid . $node->model . $node->list_price . $node->cost . $node->sell_price . $node->weight . $node->weight_units . $node->length . $node->width . $node->height . $node->length_units . $node->pkg_qty . $node->default_qty . $node->shippable . time());
  }
  db_query("INSERT INTO {uc_products} (vid, nid, model, list_price, cost, sell_price, weight, weight_units, length, width, height, length_units, pkg_qty, default_qty, unique_hash, ordering, shippable) VALUES (%d, %d, '%s', %f, %f, %f, %f, '%s', %f, %f, %f, '%s', %d, %d, '%s', %d, %d)", $node->vid, $node->nid, $node->model, $node->list_price, $node->cost, $node->sell_price, $node->weight, $node->weight_units, $node->length, $node->width, $node->height, $node->length_units, $node->pkg_qty, $node->default_qty, $node->unique_hash, $node->ordering, $node->shippable);
}

/**
 * Implementation of hook_update().
 */
function uc_product_update($node) {
  if ($node->revision) {
    db_query("INSERT INTO {uc_products} (vid, nid, model, list_price, cost, sell_price, weight, weight_units, length, width, height, length_units, pkg_qty, default_qty, unique_hash, ordering, shippable) VALUES (%d, %d, '%s', %f, %f, %f, %f, '%s', %f, %f, %f, '%s', %d, %d, '%s', %d, %d)", $node->vid, $node->nid, $node->model, $node->list_price, $node->cost, $node->sell_price, $node->weight, $node->weight_units, $node->length, $node->width, $node->height, $node->length_units, $node->pkg_qty, $node->default_qty, $node->unique_hash, $node->ordering, $node->shippable);
  }
  else {

    //drupal_set_message('<pre>'. print_r($node, true) .'</pre>');drupal_set_message('<pre>'. print_r($node, true) .'</pre>');
    db_query("UPDATE {uc_products} SET model = '%s', list_price = %f, cost = %f, sell_price = %f, weight = %f, weight_units = '%s', length = %f, width = %f, height = %f, length_units = '%s', pkg_qty = %d, default_qty = %d, ordering = %d, shippable = %d WHERE vid = %d", $node->model, $node->list_price, $node->cost, $node->sell_price, $node->weight, $node->weight_units, $node->length, $node->width, $node->height, $node->length_units, $node->pkg_qty, $node->default_qty, $node->ordering, $node->shippable, $node->vid);
  }
}

/**
 * Implementation of hook_load().
 */
function uc_product_load(&$node) {
  return db_fetch_object(db_query('SELECT model, list_price, cost, sell_price, weight, weight_units, length, width, height, length_units, pkg_qty, default_qty, unique_hash, ordering, shippable FROM {uc_products} WHERE vid = %d', $node->vid));
}

/**
 * Implementation of hook_delete().
 */
function uc_product_delete(&$node) {
  db_query("DELETE from {uc_products} WHERE nid = %d", $node->nid);
}

/**
 * Implementation of hook_view().
 */
function uc_product_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,
  ));

  //drupal_set_message('<pre>'. print_r($node->field_image_cache, true) .'</pre>');
  if (isset($node->field_image_cache) && file_exists($node->field_image_cache[0]['filepath'])) {
    $node->content['image'] = array(
      '#value' => theme('uc_product_image', $node->field_image_cache, $teaser, $page),
      '#access' => $enabled['image'] && module_exists('imagecache'),
      '#weight' => $weight['image'],
    );
  }
  $node->content['display_price'] = array(
    '#value' => theme('uc_product_display_price', $node->sell_price, $teaser, $page),
    '#access' => $enabled['display_price'],
    '#weight' => $weight['display_price'],
  );
  if (!$teaser) {
    $node->content['model'] = array(
      '#value' => theme('uc_product_model', $node->model, $teaser, $page),
      '#access' => $enabled['model'],
      '#weight' => $weight['model'],
    );
    $node->content['body']['#weight'] = 1;
    $node->content['list_price'] = array(
      '#value' => theme('uc_product_price', $node->list_price, 'list_price', $teaser, $page),
      '#access' => $enabled['list_price'],
      '#weight' => $weight['list_price'],
    );
    $node->content['cost'] = array(
      '#value' => theme('uc_product_price', $node->cost, 'cost', $teaser, $page),
      '#access' => $enabled['cost'] && user_access('administer products'),
      '#weight' => $weight['cost'],
    );
  }
  else {
    $node->content['#attributes'] = array(
      'style' => 'display: inline',
    );
  }
  $node->content['sell_price'] = array(
    '#value' => theme('uc_product_sell_price', $node->sell_price, $teaser, $page),
    '#access' => $enabled['sell_price'],
    '#weight' => $weight['sell_price'],
  );
  if (!$teaser) {
    $node->content['weight'] = array(
      '#value' => theme('uc_product_weight', $node->weight, $node->weight_units, $teaser, $page),
      '#access' => $enabled['weight'],
      '#weight' => $weight['weight'],
    );
    $node->content['dimensions'] = array(
      '#value' => theme('uc_product_dimensions', $node->length, $node->width, $node->height, $node->length_units, $teaser, $page),
      '#access' => $enabled['dimensions'],
      '#weight' => $weight['dimensions'],
    );
    if (module_exists('uc_cart')) {
      $node->content['add_to_cart'] = array(
        '#value' => theme('uc_product_add_to_cart', $node, $teaser, $page),
        '#access' => $enabled['add_to_cart'],
        '#weight' => $weight['add_to_cart'],
      );
    }
  }
  else {
    if (module_exists('uc_cart') && variable_get('uc_product_add_to_cart_teaser', true)) {
      $node->content['add_to_cart'] = array(
        '#value' => theme('uc_product_add_to_cart', $node, $teaser, $page),
        '#access' => $enabled['add_to_cart'],
        '#weight' => $weight['add_to_cart'],
      );
    }
  }

  //drupal_set_message('<pre>'. print_r($breadcrumb, true) .'</pre>');
  return $node;
}
function uc_product_form_alter($form_id, &$form) {
  if ($form_id == 'search_form' && arg(0) == 'admin' && arg(1) == 'store' && arg(2) == 'products' && user_access('use advanced search')) {

    // Keyword boxes:
    $form['advanced'] = array(
      '#type' => 'fieldset',
      '#title' => t('Advanced search'),
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
      '#attributes' => array(
        'class' => 'search-advanced',
      ),
    );
    $form['advanced']['keywords'] = array(
      '#prefix' => '<div class="criterion">',
      '#suffix' => '</div>',
    );
    $form['advanced']['keywords']['or'] = array(
      '#type' => 'textfield',
      '#title' => t('Containing any of the words'),
      '#size' => 30,
      '#maxlength' => 255,
    );
    $form['advanced']['keywords']['phrase'] = array(
      '#type' => 'textfield',
      '#title' => t('Containing the phrase'),
      '#size' => 30,
      '#maxlength' => 255,
    );
    $form['advanced']['keywords']['negative'] = array(
      '#type' => 'textfield',
      '#title' => t('Containing none of the words'),
      '#size' => 30,
      '#maxlength' => 255,
    );

    // Taxonomy box:
    if ($taxonomy = module_invoke('taxonomy', 'form_all', 1)) {
      $form['advanced']['category'] = array(
        '#type' => 'select',
        '#title' => t('Only in the category(s)'),
        '#prefix' => '<div class="criterion">',
        '#size' => 10,
        '#suffix' => '</div>',
        '#options' => $taxonomy,
        '#multiple' => TRUE,
      );
    }

    // Node types:
    $types = array();
    $product_types = module_invoke_all('product_types');
    $node_types = module_invoke_all('node_info');
    foreach ($product_types as $id) {
      $types[$id] = $node_types[$id]['name'];
    }
    $form['advanced']['type'] = array(
      '#type' => 'checkboxes',
      '#title' => t('Only of the type(s)'),
      '#prefix' => '<div class="criterion">',
      '#suffix' => '</div>',
      '#options' => $types,
    );
    $form['advanced']['submit'] = array(
      '#type' => 'submit',
      '#value' => t('Advanced search'),
      '#prefix' => '<div class="action clear-block">',
      '#suffix' => '</div>',
    );
    $form['#validate']['node_search_validate'] = array();
  }
}

/* function uc_product_search($op, $keys = null) {
  switch ($op) {
    case 'name':
      return t('Products');
  }
} */

/******************************************************************************
 * TAPIr Hooks                                                                *
 ******************************************************************************/

/**
 * Define up the product list table.
 *
 * @see uc_product_table
 */
function uc_product_table_settings() {
  $tables = array();
  $tables[] = array(
    'id' => 'uc_product_table',
    'description' => t('Lists a group of products in an abbreviated format.'),
    'path' => 'admin/store/settings/tables',
    'access' => 'administer store',
    'preview' => FALSE,
  );
  return $tables;
}

/**
 * Display product fields in a TAPIr table.
 *
 * Display image, name, price, and add to cart form.
 */
function uc_product_table($op, $args = array()) {
  switch ($op) {
    case 'fields':
      $fields = array();
      $fields[] = array(
        'name' => 'image',
        'title' => t('Image'),
        'weight' => -5,
        'enabled' => module_exists('imagecache'),
      );
      $fields[] = array(
        'name' => 'name',
        'title' => t('Name'),
        'weight' => 0,
        'enabled' => true,
        'attributes' => array(
          'field' => 'n.title',
        ),
      );
      $fields[] = array(
        'name' => 'list_price',
        'title' => t('List price'),
        'weight' => 3,
        'enabled' => false,
        'attributes' => array(
          'field' => 'p.list_price',
        ),
      );
      $fields[] = array(
        'name' => 'price',
        'title' => t('Price'),
        'weight' => 5,
        'enabled' => true,
        'attributes' => array(
          'field' => 'p.sell_price',
        ),
      );
      if (module_exists('uc_cart') && (arg(0) != 'admin' || $_GET['q'] == 'admin/store/settings/tables/uc_product_table')) {
        $fields[] = array(
          'name' => 'add_to_cart',
          'title' => t('Add to cart'),
          'weight' => 10,
          'enabled' => false,
          'attributes' => array(
            'nowrap' => 'nowrap',
          ),
        );
      }
      return $fields;
    case 'data':
      $data = array();
      foreach ($args['nids'] as $nid) {
        $node = node_load($nid);
        if ($node->type != 'image') {
          if (module_exists('imagecache') && isset($node->field_image_cache) && file_exists($node->field_image_cache[0]['filepath'])) {
            $data['image'][] = l(theme('imagecache', 'product_list', $node->field_image_cache[0]['filepath'], $node->field_image_cache[0]['alt'], $node->field_image_cache[0]['title']), 'node/' . $node->nid, array(), null, null, false, true);
          }
          else {
            $data['image'][] = t('n/a');
          }
          $data['name'][] = array(
            'data' => l($node->title, 'node/' . $node->nid),
            'width' => '100%',
          );
          $data['list_price'][] = array(
            'data' => theme('uc_product_price', $node->list_price, 'list_price'),
            'nowrap' => 'nowrap',
          );
          $data['price'][] = array(
            'data' => theme('uc_product_sell_price', $node->sell_price, true),
            'nowrap' => 'nowrap',
          );
          if (module_exists('uc_cart') && arg(0) != 'admin') {
            $data['add_to_cart'][] = drupal_get_form('uc_catalog_buy_it_now_form_' . $node->nid, $node);
          }
        }
      }
      return $data;
    case 'attributes':
      return $args['attributes'];
  }
}

/**
 * @ingroup forms
 * @see uc_product_forms
 * @see uc_catalog_buy_it_now_form_submit
 */
function uc_catalog_buy_it_now_form($node) {
  $form = array();
  $form['#base'] = 'uc_catalog_buy_it_now_form';
  $form['nid'] = array(
    '#type' => 'hidden',
    '#value' => $node->nid,
  );
  $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',
    ),
  );
  return $form;
}
function uc_catalog_buy_it_now_form_submit($form_id, $form_values) {
  $node = node_load($form_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');
      return drupal_get_path_alias('node/' . $form_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');
          return drupal_get_path_alias('node/' . $form_values['nid']);
        }
      }
    }
  }
  return uc_cart_add_item($form_values['nid'], 1, module_invoke_all('add_to_cart_data', $form_values));
}

/******************************************************************************
 * Token Hooks                                                                *
 ******************************************************************************/

/**
 * Provide product token values.
 */
function uc_product_token_values($type, $object = NULL, $options = array()) {
  if ($type == 'node' && uc_product_is_product($object)) {
    $tokens = array();
    $tokens['model'] = $object->model;
    $tokens['list_price'] = $object->list_price;
    $tokens['cost'] = $object->cost;
    $tokens['sell_price'] = $object->sell_price;
    $tokens['weight_units'] = $object->weight_units;
    $tokens['weight-raw'] = $object->weight;
    $tokens['weight'] = uc_weight_format($object->weight, $object->weight_units);
    $tokens['length_units'] = $object->length_units;
    $tokens['length-raw'] = $object->length;
    $tokens['length'] = uc_length_format($object->length, $object->length_units);
    $tokens['width-raw'] = $object->width;
    $tokens['width'] = uc_length_format($object->width, $object->length_units);
    $tokens['height-raw'] = $object->height;
    $tokens['height'] = uc_length_format($object->height, $object->length_units);
    return $tokens;
  }
}
function uc_product_token_list($type = 'all') {
  if ($type == 'node' || $type == 'product' || $type == 'ubercart' || $type == 'all') {
    $tokens = array();
    $tokens['product']['model'] = t("The product's model number.");
    $tokens['product']['list_price'] = t("The product's list price.");
    $tokens['product']['cost'] = t("The product's cost.");
    $tokens['product']['sell_price'] = t("The product's sell price.");
    $tokens['product']['weight_units'] = t("The unit of measurement for the product's weight.");
    $tokens['product']['weight-raw'] = t("The numerical value of the product's weight.");
    $tokens['product']['weight'] = t("The product's formatted weight.");
    $tokens['product']['length_units'] = t("The unit of measurement for the product's length, width, and height.");
    $tokens['product']['length-raw'] = t("The numerical value of the product's length.");
    $tokens['product']['length'] = t("The product's formatted length.");
    $tokens['product']['width-raw'] = t("The numerical value of the product's width.");
    $tokens['product']['width'] = t("The product's formatted width.");
    $tokens['product']['height-raw'] = t("The numerical value of the product's height.");
    $tokens['product']['height'] = t("The product's formatted height.");
    return $tokens;
  }
}

/******************************************************************************
 * Views Hooks                                                                *
 ******************************************************************************/

/**
 * Implementation of hook_views_tables()
 *
 * @see uc_views_handler_price
 * @see uc_views_handler_weight
 * @see uc_product_views_handler_add_to_cart_link
 * @see uc_product_views_handler_filter_product
 */
function uc_product_views_tables() {
  $tables['uc_products'] = array(
    'name' => 'uc_products',
    'join' => array(
      'left' => array(
        'table' => 'node',
        'field' => 'vid',
      ),
      'right' => array(
        'field' => 'vid',
      ),
    ),
    'fields' => array(
      'model' => array(
        'name' => t('Product: SKU'),
        'help' => t('Model number'),
        'handler' => 'views_handler_field_nodelink',
        'option' => array(
          '#type' => 'select',
          '#options' => array(
            'link' => 'As link',
            'nolink' => 'Without link',
          ),
        ),
        'sortable' => TRUE,
      ),
      'list_price' => array(
        'name' => t('Product: List price'),
        'help' => t("Manufacturer's suggested price"),
        'handler' => 'uc_views_handler_price',
        'sortable' => TRUE,
      ),
      'cost' => array(
        'name' => t('Product: Cost'),
        'help' => t('Amount to buy the unit'),
        'handler' => 'uc_views_handler_price',
        'sortable' => TRUE,
      ),
      'sell_price' => array(
        'name' => t('Product: Sell price'),
        'help' => t('Amount to sell the unit'),
        'handler' => 'uc_views_handler_price',
        'sortable' => TRUE,
      ),
      'weight' => array(
        'name' => t('Product: Weight'),
        'help' => t('Physical weight'),
        'handler' => 'uc_views_handler_weight',
        'sortable' => TRUE,
      ),
      'addtocartlink' => array(
        'name' => t('Product: Add to cart link'),
        'help' => t("Form to put the product in the customer's cart."),
        'sortable' => FALSE,
        'notafield' => TRUE,
        'handler' => 'uc_product_views_handler_add_to_cart_link',
      ),
      'buyitnowbutton' => array(
        'name' => t('Product: Buy it now button'),
        'help' => t('A button to add a product to the cart without quantity or attribute fields.'),
        'sortable' => FALSE,
        'notafield' => TRUE,
        'handler' => 'uc_product_views_handler_buy_it_now_button',
      ),
    ),
    'sorts' => array(
      'model' => array(
        'name' => t('Product: SKU'),
      ),
      'list_price' => array(
        'name' => t('Product: List price'),
      ),
      'cost' => array(
        'name' => t('Product: Cost'),
      ),
      'sell_price' => array(
        'name' => t('Product: Sell price'),
      ),
      'weight' => array(
        'name' => t('Product: Weight'),
      ),
      'ordering' => array(
        'name' => t('Product: List Order'),
      ),
    ),
    'filters' => array(
      'is_product' => array(
        'name' => t('Product: Is a product'),
        'operator' => array(
          '=' => 'Equals',
        ),
        'list' => 'views_handler_operator_yesno',
        'list-type' => 'select',
        'handler' => 'uc_product_views_handler_filter_product',
        'help' => t('Filter the node based on whether or not it is derived from content type Product.'),
      ),
      'model' => array(
        'name' => t('Product: SKU'),
        'operator' => 'views_handler_operator_like',
        'handler' => 'views_handler_filter_like',
        'help' => t('This filter allows nodes to be filtered by their model number.'),
      ),
      'list_price' => array(
        'name' => t('Product: List Price'),
        'operator' => array(
          '=' => 'Equals',
          '<' => 'Less Than',
          '>' => 'Greater Than',
        ),
        'handler' => 'views_handler_filter_numeric',
        'help' => t('Filter the node based on whether or not it matches list price criteria.'),
      ),
      'cost' => array(
        'name' => t('Product: Cost'),
        'operator' => array(
          '=' => 'Equals',
          '<' => 'Less Than',
          '>' => 'Greater Than',
        ),
        'handler' => 'views_handler_filter_numeric',
        'help' => t('Filter the node based on whether or not it matches cost criteria.'),
      ),
      'sell_price' => array(
        'name' => t('Product: Sell Price'),
        'operator' => array(
          '=' => 'Equals',
          '<' => 'Less Than',
          '>' => 'Greater Than',
        ),
        'handler' => 'views_handler_filter_numeric',
        'help' => t('Filter the node based on whether or not it matches sell price criteria.'),
      ),
      'weight' => array(
        'name' => t('Product: Weight'),
        'operator' => array(
          '=' => 'Equals',
          '<' => 'Less Than',
          '>' => 'Greater Than',
        ),
        'handler' => 'views_handler_filter_numeric',
        'help' => t('Filter the node based on whether or not it matches weight criteria.'),
      ),
    ),
  );
  return $tables;
}

/**
 * Conditionally add editablefields support.
 */
function uc_product_views_tables_alter(&$tables) {
  if (module_exists('editablefields')) {
    if (is_array($tables['uc_products']['fields']['model']['option'])) {
      $tables['uc_products']['fields']['model']['option']['#options']['editable'] = t('Editable');
    }
    else {
      $tables['uc_products']['fields']['model']['option'] = array(
        '#type' => 'select',
        '#options' => array(
          'display' => t('Display'),
          'editable' => t('Editable'),
        ),
      );
    }
    $tables['uc_products']['fields']['model']['form_parents'] = 'base][model';
    if (is_array($tables['uc_products']['fields']['list_price']['option'])) {
      $tables['uc_products']['fields']['list_price']['option']['#options']['editable'] = t('Editable');
    }
    else {
      $tables['uc_products']['fields']['list_price']['option'] = array(
        '#type' => 'select',
        '#options' => array(
          'display' => t('Display'),
          'editable' => t('Editable'),
        ),
      );
    }
    $tables['uc_products']['fields']['list_price']['form_parents'] = 'base][prices][list_price';
    if (is_array($tables['uc_products']['fields']['cost']['option'])) {
      $tables['uc_products']['fields']['cost']['option']['#options']['editable'] = t('Editable');
    }
    else {
      $tables['uc_products']['fields']['cost']['option'] = array(
        '#type' => 'select',
        '#options' => array(
          'display' => t('Display'),
          'editable' => t('Editable'),
        ),
      );
    }
    $tables['uc_products']['fields']['cost']['form_parents'] = 'base][prices][cost';
    if (is_array($tables['uc_products']['fields']['sell_price']['option'])) {
      $tables['uc_products']['fields']['sell_price']['option']['#options']['editable'] = t('Editable');
    }
    else {
      $tables['uc_products']['fields']['sell_price']['option'] = array(
        '#type' => 'select',
        '#options' => array(
          'display' => t('Display'),
          'editable' => t('Editable'),
        ),
      );
    }
    $tables['uc_products']['fields']['sell_price']['form_parents'] = 'base][prices][sell_price';
    if (is_array($tables['uc_products']['fields']['weight']['option'])) {
      $tables['uc_products']['fields']['weight']['option']['#options']['editable'] = t('Editable');
    }
    else {
      $tables['uc_products']['fields']['weight']['option'] = array(
        '#type' => 'select',
        '#options' => array(
          'display' => t('Display'),
          'editable' => t('Editable'),
        ),
      );
    }
    $tables['uc_products']['fields']['weight']['form_parents'] = 'base][weight';
  }
}

/**
 * Return a formatted price value to display in the View.
 */
function uc_views_handler_price($fieldinfo, $fielddata, $value, $data) {
  return uc_currency_format($value);
}

/**
 * Return a formatted weight value to display in the View.
 */
function uc_views_handler_weight($fieldinfo, $fielddata, $value, $data) {
  return uc_weight_format($value);
}

/**
 * Return a formatted add to cart form to display in the View.
 */
function uc_product_views_handler_add_to_cart_link($fieldinfo, $fielddata, $value, $data) {
  $types = module_invoke_all('node_info');
  $product = node_load($data->nid);
  $module = $types[$product->type]['module'];
  if (theme_get_function($module . '_add_to_cart')) {
    return theme($module . '_add_to_cart', $product);
  }
  else {
    return theme('uc_product_add_to_cart', $product);
  }
}

/**
 * Return a formatted buy it now button to display in the View.
 */
function uc_product_views_handler_buy_it_now_button($fieldinfo, $fielddata, $value, $data) {
  $product = node_load($data->nid);
  return drupal_get_form('uc_catalog_buy_it_now_form_' . $data->nid, $product);
}

/**
 * Filter out nodes that are not products.
 */
function uc_product_views_handler_filter_product($op, $filter, $filterinfo, &$query) {
  $types = module_invoke_all('product_types');
  switch ($op) {
    case 'handler':
      switch ($filter['value'][0]) {
        case '0':
          $query
            ->add_where("node.type NOT IN ('" . implode("','", $types) . "')");
          break;
        case '1':
          $query
            ->add_where("node.type IN ('" . implode("','", $types) . "')");
          break;
      }
      break;
  }
}

/**
 * Provide a default products view.
 */
function uc_product_views_default_views() {
  $view = new stdClass();
  $view->name = 'products';
  $view->description = 'products';
  $view->access = array();
  $view->view_args_php = '';
  $view->page = TRUE;
  $view->page_title = 'Products';
  $view->page_header = '';
  $view->page_header_format = '1';
  $view->page_footer = '';
  $view->page_footer_format = '1';
  $view->page_empty = '';
  $view->page_empty_format = '1';
  $view->page_type = 'table';
  $view->url = 'products';
  $view->use_pager = TRUE;
  $view->nodes_per_page = '10';
  $view->menu = TRUE;
  $view->menu_title = '';
  $view->menu_tab = FALSE;
  $view->menu_tab_weight = '0';
  $view->menu_tab_default = FALSE;
  $view->menu_tab_default_parent = NULL;
  $view->menu_tab_default_parent_type = 'tab';
  $view->menu_parent_tab_weight = '0';
  $view->menu_parent_title = '';
  $view->sort = array(
    array(
      'tablename' => 'uc_products',
      'field' => 'ordering',
      'sortorder' => 'ASC',
      'options' => '',
    ),
    array(
      'tablename' => 'node',
      'field' => 'title',
      'sortorder' => 'ASC',
      'options' => '',
    ),
  );
  $view->argument = array();
  $view->field = array(
    array(
      'tablename' => 'node_data_field_image_cache',
      'field' => 'field_image_cache_fid',
      'label' => 'Image(s)',
      'handler' => 'content_views_field_handler_group',
      'options' => 'product_list_linked',
    ),
    array(
      'tablename' => 'node',
      'field' => 'title',
      'label' => 'Description',
      'handler' => 'views_handler_field_nodelink',
      'sortable' => '1',
      'options' => 'link',
    ),
    array(
      'tablename' => 'uc_products',
      'field' => 'sell_price',
      'label' => 'Price',
      'sortable' => '1',
    ),
  );
  $view->filter = array(
    array(
      'tablename' => 'node',
      'field' => 'status',
      'operator' => '=',
      'options' => '',
      'value' => '1',
    ),
    array(
      'tablename' => 'uc_products',
      'field' => 'is_product',
      'operator' => '=',
      'options' => '',
      'value' => '1',
    ),
  );
  $view->exposed_filter = array();
  $view->requires = array(
    'node_data_field_image_cache',
    'node',
    'uc_products',
  );
  $views[$view->name] = $view;
  return $views;
}

/******************************************************************************
 * Übercart Hooks                                                             *
 ******************************************************************************/
function uc_product_product_types() {
  return array_keys(uc_product_node_info());
}

/**
 * Display the status of the product image handlers.
 *
 * @see uc_product_image_defaults
 */
function uc_product_store_status() {
  if (!module_exists('imagefield') || !module_exists('imagecache')) {
    $description = t('To automatically configure core image support, <a href="!url">enable</a> the <a href="http://drupal.org/project/cck">Content</a>, <a href="http://drupal.org/project/imagefield">CCK Image field</a>, and <a href="http://drupal.org/project/imagecache">Imagecache</a> modules. This action is not required.', array(
      '!url' => url('admin/build/modules'),
    ));
  }
  else {
    $result = db_query("SELECT field_name, type FROM {node_field} WHERE field_name = 'field_image_cache' AND type = 'image'");
    $field_check = (bool) db_num_rows($result);
    $result = db_query("SELECT field_name, type_name FROM {node_field_instance} WHERE field_name = 'field_image_cache' AND type_name = 'product'");
    $product_field_check = (bool) db_num_rows($result);
    $presets = array(
      'product',
      'product_list',
      'uc_thumbnail',
    );
    if (module_exists('uc_catalog')) {
      $presets[] = 'uc_category';
    }
    if (module_exists('uc_cart')) {
      $presets[] = 'cart';
    }
    if (module_exists('uc_manufacturer')) {
      $presets[] = 'manufacturer';
    }
    $result = db_query("SELECT presetid, presetname FROM {imagecache_preset} WHERE presetname IN ('" . implode("','", $presets) . "')");
    $preset_check = db_num_rows($result) == count($presets);
    $actions = array();
    $good_presets = array();
    while ($preset_id = db_fetch_array($result)) {
      $good_presets[] = $preset_id['presetname'];
      $actions[$preset_id['presetid']] = db_result(db_query("SELECT actionid FROM {imagecache_action} WHERE presetid = %d", $preset_id['presetid']));
    }
    $missing_presets = array_diff($presets, $good_presets);
    $action_check = count(array_filter($actions)) == count($presets);
    if ($field_check && $product_field_check && $preset_check && $action_check) {
      $description = t('Product image support has been automatically configured by Ubercart.');
    }
    else {
      $checks = ($field_check << 3) + ($product_field_check << 2) + ($preset_check << 1) + $action_check;
      $description = t('<a href="!url">Click here</a> to automatically configure the following items for core image support:', array(
        '!url' => url('admin/store/settings/products/defaults/' . $checks),
      ));
      if (!$field_check) {
        $items[] = t('The Image field has not been created for CCK.');
      }
      if (!$product_field_check) {
        $items[] = t("The Image field has not been attached to Product nodes.");
      }
      if (!$preset_check) {
        $items[] = t('The expected Imagecache presets ("!presets") have not been created.', array(
          '!presets' => implode('", "', $missing_presets),
        ));
      }
      if (!$action_check) {
        $items[] = t('The Imagecache presets do not contain actions to perform on images. Images may be displayed in their original formats.');
      }
      $description .= theme('item_list', $items) . t('(This action is not required and should not be taken if you do not need images or have implemented your own image support.)');
    }
  }
  return array(
    array(
      'status' => 'ok',
      'title' => t('Images'),
      'desc' => $description,
    ),
  );
}

/**
 * Implementation of Übercart hook_cart_display().
 */
function uc_product_cart_display($item) {
  $node = node_load($item->nid);
  $element = array();
  $element['nid'] = array(
    '#type' => 'value',
    '#value' => $node->nid,
  );
  $element['module'] = array(
    '#type' => 'value',
    '#value' => 'uc_product',
  );
  $element['remove'] = array(
    '#type' => 'checkbox',
  );
  $element['options'] = '';
  if (module_exists('uc_attribute') && is_array($item->options)) {
    foreach ($item->options as $option) {
      $op_names[] = t('@attribute: @option', array(
        '@attribute' => $option['attribute'],
        '@option' => $option['name'],
      ));
    }
    $element['options'] = array(
      '#value' => theme('item_list', $op_names, NULL, 'ul', array(
        'class' => 'cart-options',
      )),
    );
  }
  $element['title'] = array(
    '#value' => node_access('view', $node) ? l($item->title, 'node/' . $node->nid) : check_plain($item->title),
  );
  $element['#total'] = $item->price * $item->qty;
  $element['data'] = array(
    '#type' => 'hidden',
    '#value' => serialize($item->data),
  );
  $element['qty'] = array(
    '#type' => 'textfield',
    '#default_value' => $item->qty,
    '#size' => 5,
    '#maxlength' => 6,
  );
  return $element;
}

/**
 * Update information about a specific item in current cart.
 */
function uc_product_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) {
    uc_cart_remove_item($nid, $cid, $data);
  }
  else {
    db_query("UPDATE {uc_cart_products} SET qty = %d, changed = %d WHERE nid = %d AND cart_id = '%s' AND data = '%s'", $qty, time(), $nid, $cid, serialize($data));
    cache_clear_all();
  }

  // Rebuild the items hash
  uc_cart_get_contents(NULL, 'rebuild');
}

/**
 * Implementation of hook_add_to_cart_data().
 */
function uc_product_add_to_cart_data($form_values) {
  $node = node_load($form_values['nid']);
  return array(
    'shippable' => $node->shippable,
  );
}
function uc_product_product_class($pcid, $op) {
  switch ($op) {
    case 'insert':
      db_query("UPDATE {node_type} SET module = 'uc_product', custom = 0 WHERE type = '%s'", $pcid);
      $result = db_query("SELECT n.vid, n.nid, p.unique_hash FROM {node} AS n LEFT JOIN {uc_products} AS p ON n.vid = p.vid WHERE n.type = '%s'", $pcid);
      while ($node = db_fetch_object($result)) {
        if (!$node->unique_hash) {
          $node->weight_units = variable_get('uc_weight_unit', 'lb');
          $node->length_units = variable_get('uc_length_unit', 'in');
          $node->pkg_qty = 1;
          $node->default_qty = 1;
          $node->shippable = 1;
          uc_product_insert($node);
        }
      }
      break;
  }
}

/******************************************************************************
 * Menu Callbacks                                                             *
 ******************************************************************************/

// Displays a sortable, paged table list of products on the site.
function uc_product_default() {
  $table_nids = array();

  // Redirect to /products if we have excess URL arguments.
  if (arg(1) != '') {
    drupal_goto('products');
  }

  // Get the header info from TAPIr.
  $header = tapir_get_header('uc_product_table', array());

  // Generate our ORDER BY clause if the table has been sorted.
  $order = substr(tablesort_sql($header), 10);

  // Otherwise supply a default as a TAPIr workaround.
  if (empty($order)) {
    $order = 'p.ordering, n.title';
  }

  // Fetch the product info from the database for display.
  $result = pager_query(db_rewrite_sql("SELECT n.nid FROM {node} AS n RIGHT JOIN {uc_products} AS p ON n.vid = p.vid WHERE n.status = 1 ORDER BY " . $order), variable_get('uc_product_nodes_per_page', 10), 0, NULL);
  while ($node = db_fetch_object($result)) {
    $table_nids[] = $node->nid;
  }
  $args = array(
    'nids' => $table_nids,
  );

  // Generate the output from TAPIr with a pager.
  return tapir_get_table('uc_product_table', $args) . theme('pager');
}

/**
 * Set up imagefield and imagecache for products.
 *
 * @param $checks A bitmap describing the state of the image modules. Four
 *   checks are made to fill this bitmap: an imagefield exists, an imagefield
 *   is attached to product, the imagecache presets exist, and each preset has
 *   at least one action.
 * @see uc_product_store_status
 */
function uc_product_image_defaults($checks) {
  $field_check = $checks & 8;
  $product_field_check = $checks & 4;
  $preset_check = $checks & 2;
  $action_check = $checks & 1;
  $presets = array(
    'product',
    'product_list',
    'uc_thumbnail',
  );
  if (module_exists('uc_catalog')) {
    $presets[] = 'uc_category';
  }
  if (module_exists('uc_cart')) {
    $presets[] = 'cart';
  }
  if (module_exists('uc_manufacturer')) {
    $presets[] = 'manufacturer';
  }
  $presets = drupal_map_assoc($presets);
  if (!$field_check) {
    db_query("INSERT INTO {node_field} (field_name, type, global_settings, required, multiple, db_storage) VALUES ('field_image_cache', 'image', '%s', 0, 1, 0)", 'a:0:{}');
    content_clear_type_cache();
  }
  if (!$product_field_check) {
    $widget_settings = array(
      'max_resolution' => '0',
      'image_path' => '',
      'custom_alt' => 1,
      'custom_title' => 1,
      'teaser_preset' => null,
      'body_preset' => null,
    );
    $display_settings = array(
      'label' => array(
        'format' => 'hidden',
      ),
      'teaser' => array(
        'format' => 'hidden',
      ),
      'full' => array(
        'format' => 'hidden',
      ),
    );
    foreach (module_invoke_all('product_types') as $type) {
      db_query("INSERT INTO {node_field_instance} VALUES ('field_image_cache', '%s', -2, 'Image', 'image', '%s', '%s', '')", $type, serialize($widget_settings), serialize($display_settings));
    }
    switch ($GLOBALS['db_type']) {
      case 'mysql':
      case 'mysqli':
        db_query("CREATE TABLE IF NOT EXISTS {content_field_image_cache} (\n          `vid` int(10) unsigned NOT NULL default '0',\n          `delta` int(10) unsigned NOT NULL default '0',\n          `nid` int(10) unsigned NOT NULL default '0',\n          `field_image_cache_fid` int(11) NOT NULL default '0',\n          `field_image_cache_title` varchar(255) NOT NULL default '',\n          `field_image_cache_alt` varchar(255) NOT NULL default '',\n          PRIMARY KEY  (`vid`,`delta`)\n        ) /*!40100 DEFAULT CHARACTER SET utf8 */;");
        break;
      case 'pgsql':
        db_query("CREATE TABLE {content_field_image_cache} (\n          vid int_unsigned NOT NULL default '0',\n          delta int_unsigned NOT NULL default '0',\n          nid int_unsigned NOT NULL default '0',\n          field_image_cache_fid integer NOT NULL default '0',\n          field_image_cache_title varchar(255) NOT NULL default '',\n          field_image_cache_alt varchar(255) NOT NULL,\n          PRIMARY KEY (vid,delta)\n        );");
        break;
    }
    content_clear_type_cache();
  }
  if (!$preset_check) {
    $result = db_query("SELECT * FROM {imagecache_preset} WHERE presetname IN ('" . implode("','", $presets) . "')");
    $preset_keys = array();
    foreach ($presets as $preset) {
      $preset_keys[$preset] = 0;
    }
    $presets = $preset_keys;
    while ($preset = db_fetch_array($result)) {
      $presets[$preset['presetname']] = $preset['presetid'];
    }

    //drupal_set_message('<pre>'. print_r($presets, true) .'</pre>');
    foreach ($presets as $name => $id) {
      if ($id == 0) {
        $id = db_next_id('{imagecache_preset}_presetid');
        db_query("INSERT INTO {imagecache_preset} (presetid, presetname) VALUES (%d, '%s')", $id, $name);
      }
    }
  }
  if (!$action_check) {
    $result = db_query("SELECT ia.actionid, ip.presetid, ip.presetname FROM {imagecache_preset} AS ip LEFT JOIN {imagecache_action} AS ia ON ip.presetid = ia.presetid WHERE ip.presetname IN ('" . implode("','", array_keys($presets)) . "')");
    $presets = array();
    while ($preset = db_fetch_array($result)) {
      if (is_null($preset['actionid'])) {
        switch ($preset['presetname']) {
          case 'product':
            db_query("INSERT INTO {imagecache_action} (actionid, presetid, weight, data) VALUES (%d, %d, 0, '%s')", db_next_id('{imagecache_action}_actionid'), $preset['presetid'], 'a:4:{s:8:"function";s:5:"scale";s:3:"fit";s:6:"inside";s:5:"width";s:3:"100";s:6:"height";s:3:"100";}');
            break;
          case 'product_list':
            db_query("INSERT INTO {imagecache_action} (actionid, presetid, weight, data) VALUES (%d, %d, 0, '%s')", db_next_id('{imagecache_action}_actionid'), $preset['presetid'], 'a:4:{s:8:"function";s:5:"scale";s:3:"fit";s:6:"inside";s:5:"width";s:3:"100";s:6:"height";s:3:"100";}');
            break;
          case 'uc_thumbnail':
            db_query("INSERT INTO {imagecache_action} (actionid, presetid, weight, data) VALUES (%d, %d, 0, '%s')", db_next_id('{imagecache_action}_actionid'), $preset['presetid'], 'a:4:{s:8:"function";s:5:"scale";s:3:"fit";s:6:"inside";s:5:"width";s:2:"35";s:6:"height";s:2:"35";}');
            break;
          case 'uc_category':
            db_query("INSERT INTO {imagecache_action} (actionid, presetid, weight, data) VALUES (%d, %d, 0, '%s')", db_next_id('{imagecache_action}_actionid'), $preset['presetid'], 'a:4:{s:8:"function";s:5:"scale";s:3:"fit";s:6:"inside";s:5:"width";s:2:"96";s:6:"height";s:2:"96";}');
            break;
          case 'cart':
            db_query("INSERT INTO {imagecache_action} (actionid, presetid, weight, data) VALUES (%d, %d, 0, '%s')", db_next_id('{imagecache_action}_actionid'), $preset['presetid'], 'a:4:{s:8:"function";s:5:"scale";s:3:"fit";s:6:"inside";s:5:"width";s:2:"50";s:6:"height";s:2:"50";}');
            break;
          case 'manufacturer':
            db_query("INSERT INTO {imagecache_action} (actionid, presetid, weight, data) VALUES (%d, %d, 0, '%s')", db_next_id('{imagecache_action}_actionid'), $preset['presetid'], 'a:4:{s:8:"function";s:5:"scale";s:3:"fit";s:6:"inside";s:5:"width";s:2:"80";s:6:"height";s:2:"80";}');
            break;
        }
      }
    }
  }
  cache_clear_all('imagecache:presets', 'cache');
  drupal_goto('admin/store');
}

/**
 * List the subcategories of product administration.
 */
function uc_product_administration() {
  uc_add_js(drupal_get_path('module', 'uc_product') . '/uc_product.js', 'module');

  // Parse the URL for nodes in the list.
  $nids = array();
  foreach (func_get_args() as $nid) {
    if (intval($nid) > 0) {
      $nids[] = intval($nid);
    }
  }

  // Load all the vocabularies assigned to any product node type.
  $options = array();
  $types = module_invoke_all('product_types');
  $result = db_query("SELECT v.vid, v.name FROM {vocabulary} AS v LEFT JOIN {vocabulary_node_types} AS vnt ON v.vid = vnt.vid WHERE vnt.type IN ('" . implode("','", $types) . "')");
  while ($vocab = db_fetch_array($result)) {
    $options[$vocab['vid']] = $vocab['name'];
  }

  // Set the default vid, defaulting to the catalog if it's enabled.
  $default_vid = variable_get('uc_catalog_vid', 0);

  // If at least one vocabularies were found, display a uBrowser selector.
  if (count($options) > 0) {
    if (!in_array($default_vid, array_keys($options))) {
      $default_vid = array_shift(array_keys($options));
    }
    $output = '<div>' . t('Use the uBrowser to select as many products as you like and move them to the product container. When you are finished adding products, click the List button to reload the page showing the selected products. This effect is not cumulative and will not add products onto an existing list.') . '</div>';

    // Set the product filter for the uBrowser.
    $product_filter = implode(',', $types);

    // If more than one vocabularies were found, display a select box.
    if (count($options) > 1) {
      $form['vocabs'] = array(
        '#type' => 'select',
        '#id' => 'ubrowser-vocab-select',
        '#title' => t('Display vocabulary'),
        '#default_value' => $default_vid,
        '#options' => $options,
        '#attributes' => array(
          'onchange' => 'switch_vocabulary(this.value);',
        ),
      );
      $form['product_filter'] = array(
        '#type' => 'hidden',
        '#value' => $product_filter,
      );
      drupal_prepare_form('vocab-switcher', $form);
      $output .= drupal_render($form);
    }
    $settings = array(
      'div' => '#products-selector',
      'class' => 'product-ubrowser',
      'vid' => $default_vid,
      'filter' => $product_filter,
      'search' => 'true',
      'nids' => 'true',
      'nodesg' => t('product'),
      'nodepl' => t('products'),
      'multi' => 'true',
      'select' => 'buffer_products("' . file_create_url('') . '")',
    );
    $output .= ubrowser($settings, 'products-selector') . drupal_get_form('uc_product_buffer_form', $nids);
  }

  // If no nodes have been selected, display products 50 per page.
  if (!count($nids)) {
    $header = tapir_get_header('uc_product_table', array());
    $order = substr(tablesort_sql($header), 10);
    if (empty($order)) {
      $order = 'p.ordering, n.title';
    }
    $nids = array();
    $result = pager_query("SELECT n.nid FROM {node} AS n INNER JOIN {uc_products} AS p ON n.vid = p.vid ORDER BY " . $order, 50, 0, NULL);
    while ($product = db_fetch_object($result)) {
      $nids[] = $product->nid;
    }
  }

  // Setup the arguments to pass to the table builder function.
  $args = array(
    'nids' => $nids,
    'attributes' => array(
      'class' => 'product-list',
    ),
  );

  // Display the product table and pager if necessary.
  $output .= tapir_get_table('uc_product_table', $args) . theme('pager');
  return $output;
}

/**
 * Store a list of nodes for further processing.
 *
 * @ingroup forms
 * @see uc_product_buffer_form_submit
 */
function uc_product_buffer_form($nids) {
  foreach ($nids as $nid) {
    $node = node_load($nid);
    $buffer .= theme('imagecache', 'uc_thumbnail', $node->field_image_cache[0]['filepath'], $node->field_image_cache[0]['alt'], $node->field_image_cache[0]['title']);
  }
  $form['#attributes'] = array(
    'class' => 'product-buffer',
  );
  $form['title'] = array(
    '#value' => '<b>' . t('Selected products:') . '</b>',
  );
  $form['thumbnails'] = array(
    '#value' => '<div id="buffer-images">' . t('Use the uBrowser above to select products.') . '</div>',
  );
  $form['products'] = array(
    '#type' => 'hidden',
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Create list'),
  );
  $form['reset'] = array(
    '#type' => 'submit',
    '#value' => t('Reset list'),
  );
  return $form;
}
function uc_product_buffer_form_submit($form_id, $form_values) {
  $item = menu_get_item(menu_get_active_item());
  switch ($form_values['op']) {
    case t('Reset list'):
      return $item['path'];
    default:
      return $item['path'] . $form_values['products'];
  }
}

/**
 * Display overview of product settings.
 *
 * @see uc_product_settings_form
 * @see uc_product_field_settings_form
 */
function uc_product_settings_overview() {
  $items[] = t('Teaser and catalog pages = %text', array(
    '%text' => variable_get('uc_teaser_add_to_cart_text', t('Add to cart')),
  ));
  $items[] = t('Product view pages = %text', array(
    '%text' => variable_get('uc_product_add_to_cart_text', t('Add to cart')),
  ));
  $buttons = t('<em>Add to Cart</em> submit button text:') . theme('item_list', $items);
  $sections[] = array(
    'edit' => 'admin/store/settings/products/edit',
    'title' => t('Product settings'),
    'items' => array(
      t('Showing !number products per page.', array(
        '!number' => variable_get('uc_product_nodes_per_page', 10),
      )),
      t('The <em>Add to Cart</em> form is !status in product teasers.', array(
        '!status' => variable_get('uc_product_add_to_cart_teaser', TRUE) ? t('enabled') : t('disabled'),
      )),
      t('The Quantity field in the <em>Add to Cart</em> form is !status.', array(
        '!status' => variable_get('uc_product_add_to_cart_qty', FALSE) ? t('enabled') : t('disabled'),
      )),
      $buttons,
    ),
  );
  $options = array(
    'model' => t('Model'),
    'image' => t('Image'),
    'display_price' => t('Display price'),
    'list_price' => t('List price'),
    'cost' => t("Cost (seen only by 'administer products' permission)"),
    'sell_price' => t('Sell price'),
    'weight' => t('Weight'),
    'dimensions' => t('Dimensions'),
    'add_to_cart' => variable_get('uc_product_add_to_cart_text', t('Add to cart')),
  );
  $enabled = variable_get('uc_product_field_enabled', array(
    'model' => 'model',
    'image' => 'image',
    'display_price' => 'display_price',
    'sell_price' => 'sell_price',
    'add_to_cart' => 'add_to_cart',
  ));
  $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,
  ));
  $fields = array();
  foreach ($options as $field => $label) {
    if ($enabled[$field]) {
      $fields[$field] = array(
        'enabled' => $enabled[$field],
        'weight' => $weight[$field],
        'data' => $label,
      );
    }
  }
  uasort($fields, 'uc_weight_sort');
  $sections[] = array(
    'edit' => 'admin/store/settings/products/edit/fields',
    'title' => t('Product fields'),
    'items' => array(
      t('Displayed product fields: !list', array(
        '!list' => theme('item_list', $fields),
      )),
    ),
  );
  foreach (module_invoke_all('product_feature') as $feature) {
    $features[] = $feature['title'];
  }
  if (!empty($features)) {
    $items = array(
      t('The following features are enabled: !list', array(
        '!list' => theme('item_list', $features),
      )),
    );
  }
  else {
    $items = array(
      t('No product features enabled.'),
    );
  }
  $sections[] = array(
    'edit' => 'admin/store/settings/products/edit/features',
    'title' => t('Product features'),
    'items' => $items,
  );
  $output = theme('uc_settings_overview', $sections);
  return $output;
}

/**
 * Form to change product settings.
 *
 * @ingroup forms
 * @see uc_product_settings_overview
 */
function uc_product_settings_form() {
  $form['uc_product_nodes_per_page'] = array(
    '#type' => 'select',
    '#title' => t('Products per page'),
    '#description' => t('The number of products per page in a list.'),
    '#default_value' => variable_get('uc_product_nodes_per_page', 10),
    '#options' => drupal_map_assoc(uc_range(10, 100, 10)),
  );
  $form['uc_product_add_to_cart_qty'] = array(
    '#type' => 'checkbox',
    '#title' => t('Display an optional quantity field in the <em>Add to Cart</em> form.'),
    '#default_value' => variable_get('uc_product_add_to_cart_qty', FALSE),
  );
  $form['uc_product_add_to_cart_teaser'] = array(
    '#type' => 'checkbox',
    '#title' => t('Enable <em>Add to cart</em> forms in product node teasers.'),
    '#default_value' => variable_get('uc_product_add_to_cart_teaser', TRUE),
  );
  $form['uc_add_to_cart_text'] = array(
    '#type' => 'fieldset',
    '#title' => t('<em>Add to cart</em> button text'),
    '#description' => t('Use the textboxes to adjust the text of the submit button for <em>Add to Cart</em> forms in various places on the site.'),
    '#collapsed' => FALSE,
    '#collapsible' => FALSE,
  );
  $form['uc_add_to_cart_text']['uc_teaser_add_to_cart_text'] = array(
    '#type' => 'textfield',
    '#title' => t('Teaser forms'),
    '#description' => t('For the form displayed on teasers and catalog pages.'),
    '#default_value' => variable_get('uc_teaser_add_to_cart_text', t('Add to cart')),
  );
  $form['uc_add_to_cart_text']['uc_product_add_to_cart_text'] = array(
    '#type' => 'textfield',
    '#title' => t('Product view'),
    '#description' => t('For the form displayed on the product view page.'),
    '#default_value' => variable_get('uc_product_add_to_cart_text', t('Add to cart')),
  );
  return system_settings_form($form);
}

/**
 * Allows store administrators to control what product information is relavent to their store.
 *
 * @ingroup forms
 * @see uc_product_settings_overview
 * @see theme_uc_product_field_settings_form
 * @see uc_product_field_settings_form_submit
 */
function uc_product_field_settings_form() {
  $form = array();
  $options = array(
    'model',
    'image',
    'display_price',
    'list_price',
    'cost',
    'sell_price',
    'weight',
    'dimensions',
    'add_to_cart',
  );
  $enabled = variable_get('uc_product_field_enabled', array(
    'model' => 'model',
    'image' => 'image',
    'display_price' => 'display_price',
    'sell_price' => 'sell_price',
    'add_to_cart' => 'add_to_cart',
  ));
  $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,
  ));
  $fields = array();
  foreach ($options as $field) {
    $fields[$field] = array(
      'enabled' => $enabled[$field],
      'weight' => $weight[$field],
    );
  }
  uasort($fields, 'uc_weight_sort');
  $form['fields'] = array(
    '#tree' => true,
  );
  foreach ($fields as $label => $field) {
    $form['fields'][$label]['enabled'] = array(
      '#type' => 'checkbox',
      '#default_value' => $field['enabled'],
    );
    $form['fields'][$label]['weight'] = array(
      '#type' => 'weight',
      '#delta' => 10,
      '#default_value' => $field['weight'],
    );
  }
  $form['buttons']['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save configuration'),
  );
  $form['buttons']['reset'] = array(
    '#type' => 'submit',
    '#value' => t('Reset to defaults'),
  );
  return $form;
}

/**
 * Display the product field settings form.
 *
 * @ingroup themeable
 * @see uc_product_field_settings_form
 */
function theme_uc_product_field_settings_form($form) {
  $options = array(
    'model' => t('Model'),
    'image' => t('Image'),
    'display_price' => t('Display price'),
    'list_price' => t('List price'),
    'cost' => t("Cost (seen only by 'administer products' permission)"),
    'sell_price' => t('Sell price'),
    'weight' => t('Weight'),
    'dimensions' => t('Dimensions'),
    'add_to_cart' => variable_get('uc_product_add_to_cart_text', t('Add to cart')),
  );
  $header = array(
    t('Enable'),
    t('Product field'),
    t('Weight'),
  );
  $rows = array();
  foreach (element_children($form['fields']) as $field) {
    $row = array();
    $row[] = drupal_render($form['fields'][$field]['enabled']);
    $row[] = $options[$field];
    $row[] = drupal_render($form['fields'][$field]['weight']);
    $rows[] = $row;
  }
  $output .= theme('table', $header, $rows);
  $output .= drupal_render($form);
  return $output;
}

/**
 * Submit handler for uc_product_settings_form.
 */
function uc_product_field_settings_form_submit($form_id, $form_values) {
  if ($form_values['op'] == t('Reset to defaults')) {
    variable_del('uc_product_field_enabled');
    variable_del('uc_product_field_weight');
    drupal_set_message(t('The configuration options have been reset to their default values.'));
  }
  else {
    $enabled = array();
    $weight = array();
    foreach ($form_values['fields'] as $id => $field) {
      $enabled[$id] = $field['enabled'];
      $weight[$id] = $field['weight'];
    }
    variable_set('uc_product_field_enabled', $enabled);
    variable_set('uc_product_field_weight', $weight);
    drupal_set_message(t('The configuration options have been saved.'));
  }
}

/**
 * Display a list of product classes.
 */
function uc_product_class_default() {
  $result = db_query("SELECT * FROM {uc_product_classes}");
  $header = array(
    t('Class ID'),
    t('Name'),
    t('Description'),
    t('Operations'),
  );
  $rows = array();
  while ($class = db_fetch_object($result)) {
    $ops = array(
      l(t('edit'), 'admin/store/products/classes/' . $class->pcid . '/edit'),
      l(t('delete'), 'admin/store/products/classes/' . $class->pcid . '/delete'),
    );
    $rows[] = array(
      $class->pcid,
      $class->name,
      $class->description,
      implode(' ', $ops),
    );
  }
  if (count($rows) == 0) {
    $rows[] = array(
      array(
        'data' => t('No product classes have been defined yet.'),
        'colspan' => '5',
      ),
    );
  }
  $output = theme('table', $header, $rows);
  $output .= '<h2>' . t('Add a class') . '</h2>';
  $output .= drupal_get_form('uc_product_class_form');
  return $output;
}

/**
 *  Form builder for product classes.
 *
 * @ingroup forms
 * @see uc_product_class_form_submit
 */
function uc_product_class_form($pcid = null) {
  if ($pcid) {
    $class = uc_product_class_load($pcid);
    drupal_set_title(check_plain($class->name));
    $form['pcid'] = array(
      '#type' => 'hidden',
      '#value' => $pcid,
    );
  }
  else {
    $form['pcid'] = array(
      '#type' => 'textfield',
      '#title' => t('Class ID'),
      '#required' => true,
      '#max_length' => 32,
      '#description' => t('The machine-readable name of this content type. This text will be used for constructing the URL of the <em>create content</em> page for this content type. This name may consist only of lowercase letters, numbers, and underscores. Dashes are not allowed. Underscores will be converted into dashes when constructing the URL of the <em>create content</em> page. This name must be unique to this content type.'),
    );
  }
  $form['name'] = array(
    '#type' => 'textfield',
    '#title' => t('Class name'),
    '#default_value' => $class->name,
    '#required' => true,
  );
  $form['description'] = array(
    '#type' => 'textarea',
    '#title' => t('Description'),
    '#description' => t('This text describes the content type created for this product class to administrators.'),
    '#default_value' => $class->description,
  );
  $form['op'] = array(
    '#type' => 'submit',
    '#value' => t('Submit'),
  );
  return $form;
}

/**
 * Submit handler for uc_product_class_form.
 */
function uc_product_class_form_submit($form_id, $form_values) {
  if (uc_product_class_load($form_values['pcid'])) {
    $is_new = false;
    $pcid = $form_values['pcid'];
    db_query("UPDATE {uc_product_classes} SET name = '%s', description = '%s' WHERE pcid = '%s'", $form_values['name'], $form_values['description'], $pcid);
    uc_product_node_info(true);
    module_invoke_all('product_class', $pcid, 'update');
  }
  else {
    $is_new = true;

    // Convert whitespace to underscores, and remove other non-alphanumeric characters.
    $pcid = preg_replace(array(
      '/\\s+/',
      '/\\W/',
    ), array(
      '_',
      '',
    ), strtolower($form_values['pcid']));

    // Search for class ids of the form $pcid_# where # is any character.
    $similar = db_num_rows(db_query("SELECT pcid FROM {uc_product_classes} WHERE pcid LIKE '%s\\__' OR pcid LIKE '%s' UNION SELECT type FROM {node_type} WHERE type = '%s' AND custom = 0", $pcid, $pcid, $pcid));
    if ($similar) {
      $pcid = $pcid . '_' . $similar;
    }
    db_query("INSERT INTO {uc_product_classes} (pcid, name, description) VALUES ('%s', '%s', '%s')", $pcid, $form_values['name'], $form_values['description']);
    uc_product_node_info(true);
    variable_set('node_options_' . $pcid, variable_get('node_options_product', array(
      'status',
      'promote',
    )));
    if (module_exists('comment')) {
      variable_set('comment_' . $pcid, variable_get('comment_product', COMMENT_NODE_READ_WRITE));
    }
    module_invoke_all('product_class', $pcid, 'insert');
  }
  node_types_rebuild();
  menu_rebuild();
  if ($is_new && module_exists('imagefield')) {
    $info = content_types($pcid);
    if (!isset($info['fields']['field_image_cache'])) {
      $result = db_query("SELECT field_name FROM {node_field} WHERE field_name = 'field_image_cache' AND type = 'image'");
      if (db_num_rows($result)) {
        drupal_execute('_content_admin_field_add_existing', array(
          'type_name' => $pcid,
          'field_name' => 'field_image_cache',
        ), $pcid);
      }
    }
  }
  return 'admin/store/products/classes';
}

/**
 * Confirm the deletion of a product class.
 *
 * @see uc_product_class_delete_confirm_submit
 */
function uc_product_class_delete_confirm($class_id) {
  $result = db_query("SELECT COUNT(*) AS number FROM {node} WHERE type = '%s'", $class_id);
  $products = db_fetch_object($result);
  $form['pcid'] = array(
    '#type' => 'value',
    '#value' => $class_id,
  );
  $form['#redirect'] = 'admin/store/products/classes';
  $output = confirm_form($form, t('Be very sure you want to delete the %type product class. It will become a standard node type.', array(
    '%type' => $class_id,
  )), 'admin/store/products/classes', format_plural($products->number, 'There is @count node of this type.', 'There are @count nodes of this type.'), t('Continue'), t('Cancel'));
  return $output;
}

/**
 * Submit handler for uc_product_class_delete_confirm.
 */
function uc_product_class_delete_confirm_submit($form_id, $form_values) {
  if ($form_values['confirm']) {
    $type = node_get_types('type', $form_values['pcid']);
    $type->module = 'node';
    $type->custom = 1;
    node_type_save($type);
    db_query("DELETE FROM {uc_product_classes} WHERE pcid = '%s'", $form_values['pcid']);
    module_invoke_all('product_class', $form_values['pcid'], 'delete');
    uc_product_node_info(true);
    node_types_rebuild();
    menu_rebuild();
    drupal_set_message(t('Product class %type deleted.', array(
      '%type' => $form_values['pcid'],
    )));
  }
}

/******************************************************************************
 * Module Functions                                                           *
 ******************************************************************************/

/**
 * Determing if the given node is a product.
 *
 * @param $node If an object or array, it's "type" member is considered. If a
 *   a string, it's value is considered. If an integer, node_load() is called.
 * @return boolean
 */
function uc_product_is_product($node) {
  if (is_int($node)) {
    $node = node_load($node);
  }
  if (is_object($node)) {
    $type = $node->type;
  }
  elseif (is_array($node)) {
    $type = $node['type'];
  }
  elseif (is_string($node)) {
    $type = $node;
  }
  if (!$type) {
    return false;
  }
  $types = module_invoke_all('product_types');
  return in_array($type, $types);
}

/**
 * Format a product's model number.
 *
 * @ingroup themeable
 */
function theme_uc_product_model($model, $teaser = 0, $page = 0) {
  $output = '<div class="model">';
  $output .= t('SKU: @sku', array(
    '@sku' => $model,
  ));
  $output .= '</div>';
  return $output;
}

/**
 * Wrap the "Add to Cart" form in a <div>.
 *
 * @ingroup themeable
 */
function theme_uc_product_add_to_cart($node, $teaser = 0, $page = 0) {
  $output = '<div class="add_to_cart">';
  if ($node->nid) {
    $output .= drupal_get_form('uc_product_add_to_cart_form_' . $node->nid, $node);
  }
  else {
    $output .= drupal_get_form('uc_product_add_to_cart_form', $node);
  }
  $output .= '</div>';
  return $output;
}

/**
 * Form to add the $node product to the cart.
 *
 * @ingroup forms
 * @param $node A product node.
 * @see uc_product_forms
 * @see uc_product_add_to_cart_form_validate
 * @see uc_product_add_to_cart_form_submit
 */
function uc_product_add_to_cart_form($node) {
  $form = array();
  $form['#base'] = 'uc_product_add_to_cart_form';
  $form['nid'] = array(
    '#type' => 'value',
    '#value' => $node->nid,
  );
  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',
    ),
  );
  return $form;
}

/**
 * Allow only positive, numeric quantities.
 */
function uc_product_add_to_cart_form_validate($form_id, $form_values) {
  if (!is_numeric($form_values['qty']) || intval($form_values['qty']) <= 0) {
    form_set_error('qty', t('You have entered an invalid quantity.'));
  }
}

/**
 * Submit handler for uc_product_add_to_cart_form.
 */
function uc_product_add_to_cart_form_submit($form_id, $form_values) {
  return uc_cart_add_item($form_values['nid'], $form_values['qty'], module_invoke_all('add_to_cart_data', $form_values));
}

/**
 * Format a product's price.
 *
 * @param $price
 *   The amount to print.
 * @param $class
 *   Determines the label and the CSS class of the <div>.
 * @ingroup themeable
 */
function theme_uc_product_price($price, $class, $teaser = 0, $page = 0) {
  $output = '<div class="' . $class . '">';
  switch ($class) {
    case 'list_price':
      $output .= t('List Price: !price', array(
        '!price' => uc_currency_format($price),
      ));
      break;
    case 'cost':
      $output .= t('Cost: !price', array(
        '!price' => uc_currency_format($price),
      ));
      break;
    case 'sell_price':
    default:
      $output .= t('Price: !price', array(
        '!price' => uc_currency_format($price),
      ));
      break;
  }
  $output .= '</div>';
  return $output;
}

/**
 * Format the selling price based on the view mode.
 *
 * @param $price
 *   The price amount.
 * @param $teaser
 *   Passed from uc_product_view().
 * @ingroup themeable
 */
function theme_uc_product_sell_price($price, $teaser = 0, $page = 0) {
  if ($teaser) {
    $output = '<div class="sell_price">';
    $output .= uc_currency_format($price);
    $output .= '</div>';
  }
  else {
    $output = '<div class="sell_price">';
    $output .= t('Price: !price', array(
      '!price' => uc_currency_format($price),
    ));
    $output .= '</div>';
  }
  return $output;
}

/**
 * Format a product's weight.
 *
 * @ingroup themeable
 */
function theme_uc_product_weight($weight, $unit = null, $teaser = 0, $page = 0) {
  $output = '<div class="weight">';
  $output .= t('Weight: !weight', array(
    '!weight' => uc_weight_format($weight, $unit),
  ));
  $output .= '</div>';
  return $output;
}

/**
 * Format a product's length, width, and height.
 *
 * @ingroup themeable
 */
function theme_uc_product_dimensions($length, $width, $height, $units = null, $teaser = 0, $page = 0) {
  $output = '<div class="dimensions">';
  $output .= t('Dimensions: !length × !width × !height', array(
    '!length' => uc_length_format($length, $units),
    '!width' => uc_length_format($width, $units),
    '!height' => uc_length_format($height, $units),
  ));
  $output .= '</div>';
  return $output;
}

/**
 * Format a product's images with imagecache and Thickbox.
 *
 * @ingroup themeable
 */
function theme_uc_product_image($images, $teaser = 0, $page = 0) {
  static $rel_count = 0;
  $thickbox_enabled = module_exists('thickbox');
  $first = array_shift($images);
  $output = '<div class="product_image">';
  if ($thickbox_enabled) {
    $output .= '<a href="' . check_url(file_create_url($first['filepath'])) . '" title="' . $first['title'] . '" class="thickbox" rel="field_image_cache_' . $rel_count . '">';
  }
  $output .= theme('imagecache', 'product', $first['filepath'], $first['alt'], $first['title']);

  /* if (count($images)) {
      $output .= '<br />'. t('Click for more images.');
    } */
  if ($thickbox_enabled) {
    $output .= '</a>';
  }
  $output .= '<br />';
  foreach ($images as $thumbnail) {
    if ($thickbox_enabled) {
      $output .= '<a href="' . check_url(file_create_url($thumbnail['filepath'])) . '" title="' . $thumbnail['title'] . '" class="thickbox" rel="field_image_cache_' . $rel_count . '">';
    }
    $output .= theme('imagecache', 'uc_thumbnail', $thumbnail['filepath'], $thumbnail['alt'], $thumbnail['title']);
    if ($thickbox_enabled) {
      $output .= '</a>';
    }
  }
  $output .= '</div>';
  $rel_count++;
  return $output;
}

/**
 * @ingroup themeable
 */
function theme_uc_product_display_price($price, $teaser = 0, $page = 0) {
  $output = '<div class="display_price">';
  $output .= uc_currency_format($price);
  $output .= '</div>';
  return $output;
}

/**
 * Get the cost of a product node.
 *
 * @param $node_id
 *   nid of the selected node
 * @return
 *   float - cost
 */
function uc_product_get_cost($node_id) {
  $product = node_load($node_id);
  return $product->cost;
}

/**
 * Get the selling price of a product node.
 *
 * @param $node_id
 *   nid of the selected node
 * @return
 *   float - sell price
 */

/* function uc_product_get_price($node_id) {
  $product = node_load($node_id);
  return $product->sell_price;
} */

/**
 * Returns an HTML img tag based on a node's attached image.
 *
 * @param $node_id
 *   The node's id.
 * @param $format
 *   By default, 'uc_thumbnail', with possible values of '_original', 'thumbnail', and 'preview'.
 * @return
 *   An HTML img. When $format is 'uc_thumbnail', the image is a link to the node.
 *   When $format is 'preview', the image is a link to the image file.
 */
function uc_product_get_picture($node_id, $format = 'product') {
  $img = '';
  $product = node_load($node_id);
  if (!module_exists('imagecache')) {
    return '';
  }
  $path = $product->field_image_cache[0]['filepath'];
  if (file_exists($path)) {
    $img = theme('imagecache', $format, $path, $product->field_image_cache[0]['alt'], $product->field_image_cache[0]['title']);
    if ($format == 'product') {
      $img = '<a href="' . check_url(file_create_url($path)) . '" class="thickbox" rel="field_image_cache">' . $img . '</a>';
    }
    else {
      $img = l($img, 'node/' . $product->nid, array(), null, null, false, true);
    }
  }
  return $img;
}

/**
 * Load a product class.
 */
function uc_product_class_load($class_id) {
  static $classes = array();
  if (empty($classes[$class_id])) {
    $result = db_query("SELECT * FROM {uc_product_classes} WHERE pcid = '%s'", $class_id);
    $class = db_fetch_object($result);
    $classes[$class_id] = $class;
  }
  return $classes[$class_id];
}

/**
 * AJAX callback helper for product images.
 */
function _uc_product_get_image_field_filepath($nid) {
  $result = db_result(db_query_range("SELECT filepath FROM {files} WHERE nid = %d ORDER BY fid", $nid, 0, 1));
  if ($result) {
    print $result;
  }
  else {
    print 'false';
  }
  exit;
}

/**
 * Display the settings form for all product features.
 *
 * @ingroup forms
 */
function uc_product_feature_settings_form() {
  $titles = array();
  $features = module_invoke_all('product_feature');
  foreach ($features as $feature) {
    $titles[] = $feature['title'];
  }
  if (empty($titles)) {
    $titles[] = '<em>' . t('No product features found.') . '</em>';
  }
  $form['features_list'] = array(
    '#value' => '<div><b>' . t('The following product features are enabled') . ':</b><br />' . implode(', ', $titles) . '</div><br />',
  );
  foreach ($features as $feature) {
    if (function_exists($feature['settings'])) {
      $form[$feature['id']] = array(
        '#type' => 'fieldset',
        '#title' => t('!feature settings', array(
          '!feature' => $feature['title'],
        )),
        '#collapsible' => TRUE,
        '#collapsed' => TRUE,
      );
      $form[$feature['id']] = array_merge($form[$feature['id']], $feature['settings']());
    }
  }
  return system_settings_form($form);
}

/**
 * Displays the project features tab on a product node edit form.
 */
function uc_product_features($node) {
  drupal_set_title(check_plain($node->title));
  if (arg(4)) {

    // First check to see if we're trying to remove a feature.
    if (intval(arg(5)) > 0 && arg(6) == 'delete') {
      $result = db_query("SELECT * FROM {uc_product_features} WHERE pfid = %d AND fid = '%s'", intval(arg(5)), arg(4));
      if ($feature = db_fetch_array($result)) {

        // If the user confirmed the delete, process it!
        if ($_POST['pf_delete']) {

          // Call the delete function for this product feature if it exists.
          $func = uc_product_feature_data($feature['fid'], 'delete');
          if (function_exists($func)) {
            $func($feature);
          }

          // Remove the product feature data from the database.
          db_query("DELETE FROM {uc_product_features} WHERE pfid = %d", intval(arg(5)));
          drupal_set_message(t('The product feature has been deleted.'));
          drupal_goto('node/' . arg(1) . '/edit/features');
        }

        // Show the confirmation form for deleting this feature.
        $question = $node->title;
        $description = t('Are you sure you wish to delete this %feature?', array(
          '%feature' => uc_product_feature_data($feature['fid'], 'title'),
        )) . '<div><b>' . t('Description') . ':</b><br />' . $feature['description'] . '</div><br />';
        return drupal_get_form('confirm_form', NULL, $question, 'node/' . arg(1) . '/edit/features', $description, t('Delete'), t('Cancel'), 'pf_delete');
      }
      else {
        drupal_set_message(t("That product feature doesn't exist."), 'error');
        drupal_goto('node/' . arg(1) . '/edit/features');
      }
    }

    // Handle adding or editing product features.
    $func = uc_product_feature_data(arg(4), 'callback');
    if (function_exists($func)) {
      if (arg(5) == 'add') {
        $output = drupal_get_form($func, $node, array());
      }
      elseif (intval(arg(5)) > 0) {
        $result = db_query("SELECT * FROM {uc_product_features} WHERE pfid = %d AND fid = '%s'", intval(arg(5)), arg(4));
        if ($feature = db_fetch_array($result)) {
          $output = drupal_get_form($func, $node, $feature);
        }
      }
    }
    else {
      drupal_set_message(t('Error: Attempted to add a non-existent product feature type.'), 'error');
      drupal_goto('node/' . $node->nid . '/edit/features');
    }
    if (empty($output)) {
      drupal_set_message(t('Error: No form data was returned for that operation.'), 'error');
      drupal_goto('node/' . $node->nid . '/edit/features');
    }
    return $output;
  }
  $header = array(
    t('Type'),
    t('Description'),
    t('Operations'),
  );
  $result = db_query("SELECT * FROM {uc_product_features} WHERE nid = %d ORDER BY pfid ASC", $node->nid);
  while ($feature = db_fetch_object($result)) {
    $operations = array(
      l(t('edit'), 'node/' . $node->nid . '/edit/features/' . $feature->fid . '/' . $feature->pfid),
      l(t('delete'), 'node/' . $node->nid . '/edit/features/' . $feature->fid . '/' . $feature->pfid . '/delete'),
    );
    $rows[] = array(
      'data' => array(
        array(
          'data' => uc_product_feature_data($feature->fid, 'title'),
          'nowrap' => 'nowrap',
        ),
        array(
          'data' => $feature->description,
          'width' => '100%',
        ),
        array(
          'data' => implode(' ', $operations),
          'nowrap' => 'nowrap',
        ),
      ),
      'valign' => 'top',
    );
  }
  if (empty($rows)) {
    $rows[] = array(
      array(
        'data' => t('No features found for this product.'),
        'colspan' => 3,
      ),
    );
  }
  $output = theme('table', $header, $rows) . drupal_get_form('uc_product_feature_add_form');
  return $output;
}

/**
 * Add the form for adding a product feature to the features tab.
 *
 * @ingroup forms
 * @see theme_uc_product_feature_add_form
 */
function uc_product_feature_add_form() {
  foreach (module_invoke_all('product_feature') as $feature) {
    $options[$feature['id']] = $feature['title'];
  }
  ksort($options);
  $form['feature'] = array(
    '#type' => 'select',
    '#title' => t('Add a new feature'),
    '#options' => $options,
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Add'),
  );
  return $form;
}

/**
 * @ingroup themeable
 */
function theme_uc_product_feature_add_form($form) {
  $output = '<table class="add-feature"><tr><td>' . drupal_render($form) . '</td></tr></table>';
  return $output;
}

/**
 * Submit handler for uc_product_feature_add_form_submit.
 */
function uc_product_feature_add_form_submit($form_id, $form_values) {
  return 'node/' . arg(1) . '/edit/features/' . $form_values['feature'] . '/add';
}

/**
 * Return a bit of data from a product feature array based on the feature ID
 * and array key.
 *
 * @param $fid
 *   The string ID of the product feature you want to get data from.
 * @param $key
 *   The key in the product feature array you want: title, callback, delete,
 *     settings
 * @return
 *   The value of the key you specify.
 */
function uc_product_feature_data($fid, $key) {
  static $features;
  if (empty($features)) {
    foreach (module_invoke_all('product_feature') as $feature) {
      $features[$feature['id']] = $feature;
    }
  }
  return $features[$fid][$key];
}

/**
 * Returns a form array with some default hidden values and submit button.
 *
 * @param $form
 *   The form array you wish to add the elements to.
 * @return
 *   The form array with elements added for the nid, pfid, submit button, and
 *     cancel link.
 * @ingroup forms
 */
function uc_product_feature_form($form) {
  if (!isset($form['nid'])) {
    $form['nid'] = array(
      '#type' => 'hidden',
      '#value' => intval(arg(1)),
    );
  }
  if (!isset($form['pfid'])) {
    $form['pfid'] = array(
      '#type' => 'hidden',
      '#value' => intval(arg(5)),
    );
  }
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save feature'),
  );
  $form['cancel'] = array(
    '#value' => l(t('Cancel'), 'node/' . intval(arg(1)) . '/edit/features'),
  );
  return $form;
}

/**
 * Save a product feature to a product node.
 *
 * @param $data
 *   An array consisting of the following keys:
 *   - pfid: the numeric ID of the product feature when editing an existing one
 *   - nid: the numeric ID of the product node
 *   - fid: the string ID of the feature type
 *   - description: the string description of the feature for the overview table
 */
function uc_product_feature_save($data) {
  if (empty($data['nid']) && arg(0) == 'node' && intval(arg(1)) > 0) {
    $data['nid'] = intval(arg(1));
  }
  if (empty($data['pfid'])) {
    if (arg(0) == 'node' && arg(3) == 'features' && intval(arg(5)) > 0) {
      $data['pfid'] = intval(arg(5));
    }
    else {
      $data['pfid'] = db_next_id('{uc_product_features}_pfid');
    }
  }

  // First attempt to update an existing row.
  db_query("UPDATE {uc_product_features} SET description = '%s' WHERE pfid = %d", $data['description'], intval($data['pfid']));

  // Otherwise insert this feature as a new row.
  if (db_affected_rows() == 0) {
    db_query("INSERT INTO {uc_product_features} VALUES (%d, %d, '%s', '%s')", $data['pfid'], $data['nid'], $data['fid'], $data['description']);
    drupal_set_message(t('The product feature has been added.'));
  }
  else {
    drupal_set_message(t('The product feature has been updated.'));
  }
  return 'node/' . $data['nid'] . '/edit/features';
}

Functions

Namesort descending Description
theme_uc_product_add_to_cart Wrap the "Add to Cart" form in a <div>.
theme_uc_product_dimensions Format a product's length, width, and height.
theme_uc_product_dimensions_form Put length, width, and height fields on the same line.
theme_uc_product_display_price
theme_uc_product_feature_add_form
theme_uc_product_field_settings_form Display the product field settings form.
theme_uc_product_form_prices
theme_uc_product_form_weight
theme_uc_product_image Format a product's images with imagecache and Thickbox.
theme_uc_product_model Format a product's model number.
theme_uc_product_price Format a product's price.
theme_uc_product_sell_price Format the selling price based on the view mode.
theme_uc_product_weight Format a product's weight.
uc_catalog_buy_it_now_form
uc_catalog_buy_it_now_form_submit
uc_product_access Implementation of hook_access().
uc_product_add_to_cart_data Implementation of hook_add_to_cart_data().
uc_product_add_to_cart_form Form to add the $node product to the cart.
uc_product_add_to_cart_form_submit Submit handler for uc_product_add_to_cart_form.
uc_product_add_to_cart_form_validate Allow only positive, numeric quantities.
uc_product_administration List the subcategories of product administration.
uc_product_buffer_form Store a list of nodes for further processing.
uc_product_buffer_form_submit
uc_product_cart_display Implementation of Übercart hook_cart_display().
uc_product_class_default Display a list of product classes.
uc_product_class_delete_confirm Confirm the deletion of a product class.
uc_product_class_delete_confirm_submit Submit handler for uc_product_class_delete_confirm.
uc_product_class_form Form builder for product classes.
uc_product_class_form_submit Submit handler for uc_product_class_form.
uc_product_class_load Load a product class.
uc_product_default
uc_product_delete Implementation of hook_delete().
uc_product_disable
uc_product_enable Implementation of hook_enable().
uc_product_features Displays the project features tab on a product node edit form.
uc_product_feature_add_form Add the form for adding a product feature to the features tab.
uc_product_feature_add_form_submit Submit handler for uc_product_feature_add_form_submit.
uc_product_feature_data Return a bit of data from a product feature array based on the feature ID and array key.
uc_product_feature_form Returns a form array with some default hidden values and submit button.
uc_product_feature_save Save a product feature to a product node.
uc_product_feature_settings_form Display the settings form for all product features.
uc_product_field_settings_form Allows store administrators to control what product information is relavent to their store.
uc_product_field_settings_form_submit Submit handler for uc_product_settings_form.
uc_product_form Implementation of hook_form().
uc_product_forms Implementation of hook_forms().
uc_product_form_alter
uc_product_get_cost Get the cost of a product node.
uc_product_get_picture Returns an HTML img tag based on a node's attached image.
uc_product_help Implementation of hook_help().
uc_product_image_defaults Set up imagefield and imagecache for products.
uc_product_insert Implementation of hook_insert().
uc_product_is_product Determing if the given node is a product.
uc_product_load Implementation of hook_load().
uc_product_menu Implementation of hook_menu().
uc_product_node_info Implementation of hook_node_info().
uc_product_perm Implementation of hook_perm().
uc_product_product_class
uc_product_product_types
uc_product_settings_form Form to change product settings.
uc_product_settings_overview Display overview of product settings.
uc_product_store_status Display the status of the product image handlers.
uc_product_table Display product fields in a TAPIr table.
uc_product_table_settings Define up the product list table.
uc_product_token_list
uc_product_token_values Provide product token values.
uc_product_update Implementation of hook_update().
uc_product_update_cart_item Update information about a specific item in current cart.
uc_product_validate
uc_product_view Implementation of hook_view().
uc_product_views_default_views Provide a default products view.
uc_product_views_handler_add_to_cart_link Return a formatted add to cart form to display in the View.
uc_product_views_handler_buy_it_now_button Return a formatted buy it now button to display in the View.
uc_product_views_handler_filter_product Filter out nodes that are not products.
uc_product_views_tables Implementation of hook_views_tables()
uc_product_views_tables_alter Conditionally add editablefields support.
uc_views_handler_price Return a formatted price value to display in the View.
uc_views_handler_weight Return a formatted weight value to display in the View.
_uc_product_get_image_field_filepath AJAX callback helper for product images.