uc_product.module in Ubercart 6.2
Same filename and directory in other branches
The product module for Ubercart.
Provides information that is common to all products, and user-defined product classes for more specification.
File
uc_product/uc_product.moduleView source
<?php
/**
* @file
* The product module for Ubercart.
*
* Provides information that is common to all products, and user-defined product
* classes for more specification.
*/
/******************************************************************************
* Drupal Hooks *
******************************************************************************/
/**
* Implements hook_menu().
*/
function uc_product_menu() {
$items = array();
$items['admin/store/products'] = array(
'title' => 'Products',
'description' => 'Administer products, classes, and more.',
'access arguments' => array(
'administer products',
),
'page callback' => 'uc_product_administration',
'type' => MENU_NORMAL_ITEM,
'weight' => -2,
'file' => 'uc_product.admin.inc',
);
$items['admin/store/products/view'] = array(
'title' => 'View products',
'description' => 'Build and view a list of product nodes.',
'access arguments' => array(
'administer products',
),
'type' => MENU_NORMAL_ITEM,
'weight' => -10,
'file' => 'uc_product.admin.inc',
);
$items['admin/store/products/classes'] = array(
'title' => 'Manage classes',
'description' => 'Create and edit product node types.',
'access arguments' => array(
'administer product classes',
),
'page callback' => 'uc_product_class_default',
'type' => MENU_NORMAL_ITEM,
'weight' => -2,
'file' => 'uc_product.admin.inc',
);
$items['admin/store/settings/products'] = array(
'title' => 'Product settings',
'description' => 'Configure product settings.',
'access arguments' => array(
'administer products',
),
'page callback' => 'uc_product_settings_overview',
'type' => MENU_NORMAL_ITEM,
'file' => 'uc_product.admin.inc',
);
$items['admin/store/settings/products/overview'] = array(
'title' => 'Overview',
'weight' => -10,
'type' => MENU_DEFAULT_LOCAL_TASK,
);
$items['admin/store/settings/products/edit'] = array(
'title' => 'Edit',
'access arguments' => array(
'administer products',
),
'page callback' => 'drupal_get_form',
'page arguments' => array(
'uc_product_settings_form',
),
'weight' => -5,
'type' => MENU_LOCAL_TASK,
'file' => 'uc_product.admin.inc',
);
$items['admin/store/settings/products/edit/general'] = array(
'title' => 'Product settings',
'access arguments' => array(
'administer products',
),
'weight' => -10,
'type' => MENU_DEFAULT_LOCAL_TASK,
'file' => 'uc_product.admin.inc',
);
$items['admin/store/settings/products/edit/fields'] = array(
'title' => 'Product fields',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'uc_product_field_settings_form',
),
'access arguments' => array(
'administer products',
),
'weight' => -5,
'type' => MENU_LOCAL_TASK,
'file' => 'uc_product.admin.inc',
);
$items['admin/store/settings/products/edit/features'] = array(
'title' => 'Product features',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'uc_product_feature_settings_form',
),
'access arguments' => array(
'administer product features',
),
'weight' => 0,
'type' => MENU_LOCAL_TASK,
'file' => 'uc_product.admin.inc',
);
// Insert subitems into the edit node page for product types.
$items['node/%node/edit/product'] = array(
'title' => 'Product',
'access callback' => 'uc_product_edit_access',
'access arguments' => array(
1,
),
'weight' => -10,
'type' => MENU_DEFAULT_LOCAL_TASK,
'file' => 'uc_product.admin.inc',
);
$features = module_invoke_all('product_feature');
if (!empty($features)) {
$items['node/%node/edit/features'] = array(
'title' => 'Features',
'page callback' => 'uc_product_features',
'page arguments' => array(
1,
),
'access callback' => 'uc_product_feature_access',
'access arguments' => array(
1,
),
'weight' => 10,
'type' => MENU_LOCAL_TASK,
'file' => 'uc_product.admin.inc',
);
}
$items['admin/store/settings/products/defaults'] = array(
'access arguments' => array(
'administer products',
),
'page callback' => 'uc_product_image_defaults',
'type' => MENU_CALLBACK,
'file' => 'uc_product.admin.inc',
);
$items['admin/store/products/classes/%uc_product_class'] = array(
'title' => 'Product class',
'access arguments' => array(
'administer product classes',
),
'page callback' => 'drupal_get_form',
'page arguments' => array(
'uc_product_class_form',
4,
),
'type' => MENU_CALLBACK,
'file' => 'uc_product.admin.inc',
);
$items['admin/store/products/classes/%uc_product_class/edit'] = array(
'title' => 'Edit',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => -5,
'file' => 'uc_product.admin.inc',
);
$items['admin/store/products/classes/%uc_product_class/delete'] = array(
'access callback' => 'uc_product_class_delete_access',
'access arguments' => array(
4,
),
'page callback' => 'drupal_get_form',
'page arguments' => array(
'uc_product_class_delete_confirm',
4,
),
'type' => MENU_CALLBACK,
'file' => 'uc_product.admin.inc',
);
// Define an autocomplete path for products using the title or SKU.
$items['autocomplete/uc_product_title_sku'] = array(
'page callback' => 'uc_product_title_sku_autocomplete',
'access arguments' => array(
'access content',
),
'type' => MENU_CALLBACK,
'file' => 'uc_product.pages.inc',
);
return $items;
}
/**
* Implements hook_help().
*/
function uc_product_help($path, $arg) {
switch ($path) {
case 'admin/store/settings/products/edit/fields':
return t("These settings control which fields are displayed and the position they are displayed in when viewing product nodes.");
}
}
/**
* Implements hook_perm().
*/
function uc_product_perm() {
$perms = array(
'administer products',
'administer product classes',
'administer product features',
'administer own 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 all ' . $name . 'products';
$perms[] = 'delete own ' . $name . 'products';
$perms[] = 'delete all ' . $name . 'products';
}
}
return $perms;
}
/**
* Implements hook_access().
*/
function uc_product_access($op, $node, $account) {
$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', $account);
case 'update':
if (user_access('edit all ' . $type . 'products', $account) || user_access('edit own ' . $type . 'products', $account) && $account->uid == $node->uid) {
return TRUE;
}
break;
case 'delete':
if (user_access('delete all ' . $type . 'products', $account) || user_access('delete own ' . $type . 'products', $account) && $account->uid == $node->uid) {
return TRUE;
}
break;
}
}
/**
* Menu access callback for 'node/%node/edit/features'.
*/
function uc_product_feature_access($node) {
global $user;
return uc_product_is_product($node) && $node->type != 'product_kit' && (user_access('administer product features') || user_access('administer own product features') && $user->uid == $node->uid);
}
/**
* Menu access callback for deleting product classes.
*/
function uc_product_class_delete_access($class) {
return user_access('administer product classes') && !$class->locked;
}
/**
* Implements hook_init().
*/
function uc_product_init() {
drupal_add_css(drupal_get_path('module', 'uc_product') . '/uc_product.css');
global $conf;
$conf['i18n_variables'][] = 'uc_product_add_to_cart_text';
$conf['i18n_variables'][] = 'uc_teaser_add_to_cart_text';
}
/**
* Implements hook_enable().
*
* Sets up default imagefield and imagecache settings.
*/
function uc_product_enable() {
// For some time in Drupal 5, CCK would delete its field data if a node
// type was unavailable because its module was disabled. This function
// worked around that by giving the product classes to node.module when
// uc_product was disabled. This is no longer necessary as of CCK 5.x-1.9,
// but the workaround was left in to prevent accidents. This block of
// code is here to reclaim the product nodes after an upgrade to Drupal
// 6, and then should not be used again as the corresponding code in
// uc_product_disable() was removed.
if (variable_get('uc_product_enable_nodes', TRUE)) {
$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);
}
}
variable_set('uc_product_enable_nodes', FALSE);
}
if (module_exists('imagefield')) {
uc_product_add_default_image_field();
}
}
/**
* Implements hook_theme().
*/
function uc_product_theme() {
return array(
'uc_product_form_prices' => array(
'arguments' => array(
'form' => NULL,
),
),
'uc_product_form_weight' => array(
'arguments' => array(
'form' => NULL,
),
),
'uc_product_dimensions_form' => array(
'arguments' => array(
'form' => NULL,
),
),
'uc_product_field_settings_form' => array(
'arguments' => array(
'form' => NULL,
),
'file' => 'uc_product.admin.inc',
),
'uc_product_model' => array(
'arguments' => array(
'model' => '',
'teaser' => 0,
'page' => 0,
),
),
'uc_product_body' => array(
'arguments' => array(
'body' => '',
'teaser' => 0,
'page' => 0,
),
),
'uc_product_add_to_cart' => array(
'arguments' => array(
'node' => NULL,
'teaser' => 0,
'page' => 0,
),
),
'uc_product_price' => array(
'arguments' => array(
'price' => 0,
'context' => array(),
'options' => array(),
),
),
'uc_product_weight' => array(
'arguments' => array(
'weight' => 0,
'unit' => NULL,
'teaser' => 0,
'page' => 0,
),
),
'uc_product_dimensions' => array(
'arguments' => array(
'length' => 0,
'width' => 0,
'height' => 0,
'units' => NULL,
'teaser' => 0,
'page' => 0,
),
),
'uc_product_image' => array(
'arguments' => array(
'images' => array(),
'teaser' => 0,
'page' => 0,
),
),
'uc_product_feature_add_form' => array(
'arguments' => array(
'form' => NULL,
),
'file' => 'uc_product.admin.inc',
),
);
}
/**
* Implements hook_node_info().
*
* Creates 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();
$defaults = module_invoke_all('uc_product_default_classes');
$types['product'] = isset($defaults['product']) ? $defaults['product'] : 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,
);
$classes = uc_product_class_load(NULL, $reset);
foreach ($classes as $class) {
$class = (array) $class;
$class['module'] = 'uc_product';
$types[$class['pcid']] = $class + array(
'title_label' => $title_label,
'body_label' => $body_label,
);
}
}
return $types;
}
/**
* Implements hook_node_type().
*
* Ensures product class names and descriptions are synchronised if the
* content type is updated.
*/
function uc_product_node_type($op, $type) {
switch ($op) {
case 'update':
db_query("UPDATE {uc_product_classes} SET name = '%s', description = '%s' WHERE pcid = '%s'", $type->name, $type->description, $type->type);
break;
}
}
/**
* Implements hook_forms().
*
* Registers an "add to cart" form for each product to prevent id collisions.
*/
function uc_product_forms($form_id, $args) {
$forms = array();
if (isset($args[0]->nid) && isset($args[0]->type)) {
$product = $args[0];
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;
}
/**
* Menu access callback for 'node/%node/edit/product'.
*/
function uc_product_edit_access($node) {
// Re-inherit access callback for 'node/%node/edit'
return uc_product_is_product($node) && node_access('update', $node);
}
/**
* Implements hook_form().
*
* @see theme_uc_product_form_prices()
* @see theme_uc_product_form_weight()
* @see theme_uc_product_dimensions()
* @see uc_product_form_validate()
* @ingroup forms
*/
function uc_product_form(&$node) {
$type = node_get_types('type', $node);
$sign_flag = variable_get('uc_sign_after_amount', FALSE);
$currency_sign = variable_get('uc_currency_sign', '$');
$form['title'] = array(
'#type' => 'textfield',
'#title' => check_plain($type->title_label),
'#required' => TRUE,
'#default_value' => $node->title,
'#maxlength' => 255,
'#weight' => -5,
);
if ($type->has_body) {
$form['body_field'] = node_body_field($node, $type->body_label, $type->min_word_count);
$form['body_field']['body']['#description'] = t('Enter the product description used for product teasers and pages.');
$form['body_field']['#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' => isset($node->model) ? $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' => isset($node->list_price) ? uc_store_format_price_field_value($node->list_price) : 0,
'#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' => isset($node->cost) ? uc_store_format_price_field_value($node->cost) : 0,
'#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' => isset($node->sell_price) ? uc_store_format_price_field_value($node->sell_price) : 0,
'#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 : variable_get('uc_product_shippable_' . $node->type, 1),
'#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' => isset($node->weight) ? $node->weight : 0,
'#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' => isset($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' => isset($node->length_units) ? $node->length_units : variable_get('uc_length_unit', 'in'),
);
$form['base']['dimensions']['dim_length'] = array(
'#type' => 'textfield',
'#title' => t('Length'),
'#default_value' => isset($node->length) ? $node->length : '',
'#size' => 10,
);
$form['base']['dimensions']['dim_width'] = array(
'#type' => 'textfield',
'#title' => t('Width'),
'#default_value' => isset($node->width) ? $node->width : '',
'#size' => 10,
);
$form['base']['dimensions']['dim_height'] = array(
'#type' => 'textfield',
'#title' => t('Height'),
'#default_value' => isset($node->height) ? $node->height : '',
'#size' => 10,
);
$form['base']['pkg_qty'] = array(
'#type' => 'uc_quantity',
'#title' => t('Package quantity'),
'#default_value' => isset($node->pkg_qty) ? $node->pkg_qty : 1,
'#description' => t('At most, how many of these items can fit in your largest box? Orders that exceed this value will be split into multiple packages when retrieving shipping quotes.'),
'#weight' => 25,
);
$form['base']['default_qty'] = array(
'#type' => 'uc_quantity',
'#title' => t('Default quantity to add to cart'),
'#default_value' => isset($node->default_qty) ? $node->default_qty : 1,
'#description' => t('Use 0 to disable the quantity field next to the add to cart button.'),
'#weight' => 27,
'#allow_zero' => TRUE,
);
$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' => isset($node->ordering) ? $node->ordering : 0,
'#weight' => 30,
);
return $form;
}
/**
* Formats product price fields for node/%/edit form.
*
* @ingroup themeable
*/
function theme_uc_product_form_prices($form) {
$output = '<table><tr>';
foreach (element_children($form) as $field) {
$output .= '<td>' . drupal_render($form[$field]) . '</td>';
}
$output .= '</tr></table>';
return $output;
}
/**
* Formats product weight fields for node/%/edit form.
*
* @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>';
}
/**
* Puts 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;
}
/**
* Implements hook_validate().
*
* Ensures that prices, weight, dimensions, and quantity are positive numbers.
*/
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);
}
foreach (array(
'weight',
'length',
'width',
'height',
) as $property) {
if (!empty($node->{$property}) && (!is_numeric($node->{$property}) || $node->{$property} < 0)) {
form_set_error($property, t('@property must be a positive number. No commas and only one decimal point.', array(
'@property' => ucfirst($property),
)));
}
}
}
/**
* Implements hook_insert().
*/
function uc_product_insert($node) {
if (isset($node->dim_length)) {
$node->length = $node->dim_length;
}
if (isset($node->dim_width)) {
$node->width = $node->dim_width;
}
if (isset($node->dim_height)) {
$node->height = $node->dim_height;
}
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);
}
/**
* Implements hook_update().
*/
function uc_product_update($node) {
if (isset($node->dim_length)) {
$node->length = $node->dim_length;
}
if (isset($node->dim_width)) {
$node->width = $node->dim_width;
}
if (isset($node->dim_height)) {
$node->height = $node->dim_height;
}
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 {
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);
}
}
/**
* Implements 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));
}
/**
* Implements hook_delete().
*/
function uc_product_delete(&$node) {
db_query("DELETE from {uc_products} WHERE nid = %d", $node->nid);
}
/**
* Convenience function to get the enabled fields.
*/
function uc_product_field_enabled() {
$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,
));
return $enabled;
}
/**
* Implements hook_view().
*/
function uc_product_view($node, $teaser = 0, $page = 0) {
$node = node_prepare($node, $teaser);
$enabled = uc_product_field_enabled();
$weight = variable_get('uc_product_field_weight', array(
'image' => -2,
'display_price' => -1,
'model' => 0,
'list_price' => 2,
'cost' => 3,
'sell_price' => 4,
'weight' => 5,
'dimensions' => 6,
'add_to_cart' => 10,
));
$context = array(
'revision' => 'themed',
'type' => 'product',
'class' => array(
'product',
),
'subject' => array(
'node' => $node,
),
);
if (module_exists('imagecache') && ($field = variable_get('uc_image_' . $node->type, '')) && isset($node->{$field}) && file_exists($node->{$field}[0]['filepath'])) {
$node->content['image'] = array(
'#value' => theme('uc_product_image', $node->{$field}, $teaser, $page),
'#access' => $enabled['image'],
'#weight' => $weight['image'],
);
}
$context['class'][1] = 'display';
$context['field'] = 'sell_price';
$node->content['display_price'] = array(
'#value' => theme('uc_product_price', $node->sell_price, $context),
'#access' => $enabled['display_price'],
'#weight' => $weight['display_price'],
);
if (!$teaser) {
$node->content['model'] = array(
'#value' => theme('uc_product_model', $node->model, $teaser, $page),
'#access' => $enabled['model'],
'#weight' => $weight['model'],
);
$node->content['body']['#value'] = theme('uc_product_body', $node->body, $teaser, $page);
$node->content['body']['#weight'] = 1;
$context['class'][1] = 'list';
$context['field'] = 'list_price';
$node->content['list_price'] = array(
'#value' => theme('uc_product_price', $node->list_price, $context),
'#access' => $enabled['list_price'],
'#weight' => $weight['list_price'],
);
$context['class'][1] = 'cost';
$context['field'] = 'cost';
$node->content['cost'] = array(
'#value' => theme('uc_product_price', $node->cost, $context),
'#access' => $enabled['cost'] && user_access('administer products'),
'#weight' => $weight['cost'],
);
}
else {
$node->content['body']['#value'] = theme('uc_product_body', $node->teaser, $teaser, $page);
$node->content['#attributes'] = array(
'style' => 'display: inline',
);
}
$context['class'][1] = 'sell';
$context['field'] = 'sell_price';
$node->content['sell_price'] = array(
'#value' => theme('uc_product_price', $node->sell_price, $context, array(
'label' => !$teaser,
)),
'#access' => $enabled['sell_price'],
'#weight' => $weight['sell_price'],
);
if (!$teaser) {
$node->content['weight'] = array(
'#value' => theme('uc_product_weight', $node->weight, $node->weight_units, $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'],
);
}
}
elseif (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'],
);
}
return $node;
}
/**
* Implements hook_preprocess_node().
*
* Default product classes to use node-product.tpl.php if they don't have their
* own template.
*
* @see theme(), MODULE_preprocess_HOOK()
*/
function uc_product_preprocess_node(&$variables) {
if (uc_product_is_product($variables['type'])) {
array_unshift($variables['template_files'], 'node-product');
}
}
/**
* Implements hook_form_alter().
*
* @see uc_product_save_continue_submit()
*/
function uc_product_form_alter(&$form, $form_state, $form_id) {
// Add a button to product node forms to continue editing after saving.
if (uc_product_is_product_form($form)) {
$form['buttons']['save_continue'] = array(
'#type' => 'submit',
'#value' => t('Save and continue'),
'#weight' => 7,
'#submit' => array(
'node_form_submit',
'uc_product_save_continue_submit',
),
);
}
}
/**
* After the node is saved, redirects to the edit page.
*
* Some modules add local tasks to product edit forms, but only after it has an
* nid. Redirecting facilitates the common workflow of continuing to those
* tasks.
*/
function uc_product_save_continue_submit($form, &$form_state) {
$form_state['redirect'] = 'node/' . $form_state['nid'] . '/edit';
}
/**
* Implements hook_form_{$form_id}_alter().
*
* Adds a default image field setting to product content types.
*/
function uc_product_form_node_type_form_alter(&$form, &$form_state) {
$type = $form['#node_type'];
if (!uc_product_is_product($type->type)) {
return;
}
$form['uc_product'] = array(
'#type' => 'fieldset',
'#title' => t('Ubercart product settings'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
// shippable
$form['uc_product']['uc_product_shippable'] = array(
'#type' => 'checkbox',
'#title' => t('Product and its derivatives are shippable.'),
'#default_value' => variable_get('uc_product_shippable_' . $type->type, 1),
'#description' => t('This setting can still be overridden on the node form.'),
'#return_value' => 1,
'#weight' => -5,
);
// image field
if (module_exists('content')) {
$fields = content_types($type->type);
$fields = (array) $fields['fields'];
$options = array(
'' => t('None'),
);
foreach ($fields as $field_name => $field) {
if (strpos($field['widget']['type'], 'image') !== FALSE) {
$options[$field_name] = $field['widget']['label'];
}
}
$form['uc_product']['uc_image'] = array(
'#type' => 'select',
'#title' => t('Product image field'),
'#default_value' => variable_get('uc_image_' . $type->type, NULL),
'#options' => $options,
'#description' => t('The selected field will be used on Ubercart pages to represent the products of this content type.'),
'#weight' => -4,
);
}
}
/**
* Implements hook_i18nsync_fields_alter().
*
* Allows fields to be synchronised when translations are in use.
*/
function uc_product_i18nsync_fields_alter($fields, $type) {
if (uc_product_is_product($type)) {
$fields['uc_products']['#title'] = t('Product fields');
$fields['uc_products']['#options'] = array(
'model' => t('SKU'),
'list_price' => t('List price'),
'cost' => t('Cost'),
'sell_price' => t('Sell price'),
'weight' => t('Weight'),
'weight_units' => t('Weight units'),
'dim_length' => t('Length'),
'dim_width' => t('Width'),
'dim_height' => t('Height'),
'length_units' => t('Dimension units'),
'pkg_qty' => t('Package quantity'),
'default_qty' => t('Default quantity to add to cart'),
'ordering' => t('List position'),
);
}
}
/******************************************************************************
* CCK Hooks *
******************************************************************************/
/**
* Implements hook_content_extra_fields().
*
* Adds the "Product information"
*/
function uc_product_content_extra_fields($type_name) {
$type = node_get_types('type', $type_name);
$extra = array();
if ($type->module == 'uc_product') {
$extra['base'] = array(
'label' => t('Product information'),
'description' => t('Product module form.'),
'weight' => -1,
);
}
return $extra;
}
/**
* Implements hook_content_extra_fields_alter().
*/
function uc_product_content_extra_fields_alter(&$extra, $type_name) {
$type = node_get_types('type', $type_name);
if ($type->module == 'uc_product') {
if (isset($extra['menu'])) {
$extra['menu']['weight'] = 0;
}
if ($type->has_body) {
$extra['body_field']['weight'] = -4;
unset($extra['body_field']['view']);
$extra['body'] = array(
'label' => t('Description'),
'description' => t('Node description. (View tab)'),
'weight' => 1,
);
}
}
}
/**
* Implements hook_content_fieldapi().
*
* Resets a content type's default image field setting when that field instance
* is removed.
*/
function uc_product_content_fieldapi($op, $field) {
switch ($op) {
case 'delete instance':
if ($field->field_name == variable_get('uc_image_' . $field->type_name, NULL)) {
variable_set('uc_image_' . $field->type_name, NULL);
}
break;
}
}
/******************************************************************************
* Token Hooks *
******************************************************************************/
/**
* Implements hook_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;
}
}
/**
* Implements hook_token_list().
*/
function uc_product_token_list($type = 'all') {
$tokens = array();
if ($type == 'node' || $type == 'product' || $type == 'ubercart' || $type == 'all') {
$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;
}
/******************************************************************************
* Ubercart Hooks *
******************************************************************************/
/**
* Implements hook_product_types().
*/
function uc_product_product_types() {
return array_keys(uc_product_node_info());
}
/**
* Implements hook_store_status().
*
* Displays the status of the product image handlers.
*
* @see uc_product_image_defaults()
*/
function uc_product_store_status() {
if (!module_exists('imagefield') || !module_exists('imagecache')) {
$status = 'warning';
$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.', array(
'!url' => url('admin/build/modules'),
));
}
else {
module_load_include('inc', 'content', 'includes/content.crud');
// Check for filefields on products.
if ($field = variable_get('uc_image_product', '')) {
$instances = content_field_instance_read(array(
'field_name' => $field,
'type_name' => 'product',
));
$field_check = (bool) count($instances);
}
else {
$field_check = FALSE;
}
$presets = array(
'product',
'product_full',
'product_list',
'uc_thumbnail',
);
if (module_exists('uc_catalog')) {
$presets[] = 'uc_category';
}
if (module_exists('uc_cart')) {
$presets[] = 'cart';
}
sort($presets);
$preset_check = 1;
$action_check = 1;
foreach ($presets as $preset_name) {
$preset = imagecache_preset_by_name($preset_name);
if (empty($preset)) {
$preset_check = 0;
$action_check = 0;
break;
}
else {
if (empty($preset['actions']) && $preset_name != 'product_full') {
$action_check = 0;
break;
}
}
}
if ($field_check && $preset_check && $action_check) {
$status = 'ok';
$description = t('Product image support has been automatically configured by Ubercart.');
}
else {
$status = 'warning';
$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'),
));
if (!$field_check) {
$items[] = t('The Image file field has not been created for products.');
}
if (!$preset_check) {
$items[] = t('Some or all of the expected Imagecache presets ("!presets") have not been created. Ubercart will not display images in certain places.', array(
'!presets' => implode('", "', $presets),
));
}
if (!$action_check) {
$items[] = t('Some 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' => $status,
'title' => t('Images'),
'desc' => $description,
),
);
}
/**
* Implements 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' => 'submit',
'#value' => t('Remove'),
);
$element['title'] = array(
'#value' => node_access('view', $node) ? l($item->title, 'node/' . $node->nid) : check_plain($item->title),
);
$context = array(
'revision' => 'altered',
'type' => 'cart_item',
'subject' => array(
'cart_item' => $item,
'node' => $node,
),
);
$price_info = array(
'price' => $item->price,
'qty' => $item->qty,
);
$element['#total'] = uc_price($price_info, $context);
$element['data'] = array(
'#type' => 'hidden',
'#value' => serialize($item->data),
);
$element['qty'] = array(
'#type' => 'uc_quantity',
'#default_value' => $item->qty,
'#allow_zero' => TRUE,
);
$element['description'] = array(
'#value' => '',
);
if ($description = uc_product_get_description($item)) {
$element['description'] = array(
'#value' => $description,
);
}
return $element;
}
/**
* Implements hook_update_cart_item().
*/
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));
}
}
/**
* Implements 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,
);
}
/**
* Implements hook_product_class().
*/
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;
}
}
/******************************************************************************
* Module Functions *
******************************************************************************/
/**
* Returns the table header for the product view table.
*
* @see uc_product_table()
*/
function uc_product_table_header() {
static $columns = array();
if (empty($columns)) {
$enabled = uc_product_field_enabled();
if (module_exists('imagecache') && $enabled['image']) {
$columns['image'] = array(
'weight' => -5,
'cell' => array(
'data' => t('Image'),
),
);
}
$columns['name'] = array(
'weight' => 0,
'cell' => array(
'data' => t('Name'),
'field' => 'n.title',
),
);
if ($enabled['list_price']) {
$columns['list_price'] = array(
'weight' => 3,
'cell' => array(
'data' => t('List price'),
'field' => 'p.list_price',
),
);
}
if ($enabled['sell_price']) {
$columns['price'] = array(
'weight' => 5,
'cell' => array(
'data' => t('Price'),
'field' => 'p.sell_price',
),
);
}
if (module_exists('uc_cart') && (arg(0) != 'admin' || $_GET['q'] == 'admin/store/settings/tables/uc_product_table') && $enabled['add_to_cart']) {
$columns['add_to_cart'] = array(
'weight' => 10,
'cell' => array(
'data' => variable_get('uc_teaser_add_to_cart_text', t('Add to cart')),
'nowrap' => 'nowrap',
),
);
}
drupal_alter('tapir_table_header', $columns, 'uc_product_table');
}
return $columns;
}
/**
* Displays product fields in a TAPIr table.
*
* Displays image, name, price, and add to cart form.
*/
function uc_product_table($args = array()) {
$enabled = uc_product_field_enabled();
$table = array(
'#type' => 'tapir_table',
'#attributes' => array(
'class' => 'category-products',
),
'#columns' => uc_product_table_header(),
'#rows' => array(),
);
$context = array(
'revision' => 'themed',
'type' => 'product',
'class' => array(
'product',
),
);
$options = array(
'label' => FALSE,
);
foreach ($args as $nid) {
$data = array();
$node = node_load($nid);
if ($enabled['image']) {
if (module_exists('imagecache')) {
if (($field = variable_get('uc_image_' . $node->type, '')) && isset($node->{$field}) && file_exists($node->{$field}[0]['filepath'])) {
$image = $node->{$field}[0];
$data['image'] = array(
'#value' => l(theme('imagecache', 'product_list', $image['filepath'], $image['data']['alt'], $image['data']['title']), 'node/' . $node->nid, array(
'html' => TRUE,
)),
);
}
else {
$data['image'] = array(
'#value' => t('n/a'),
);
}
}
}
$data['name'] = array(
'#value' => l($node->title, 'node/' . $node->nid),
'#cell_attributes' => array(
'width' => '100%',
),
);
$context['subject'] = array(
'node' => $node,
);
if ($enabled['list_price']) {
$context['class'][1] = 'list';
$context['field'] = 'list_price';
$data['list_price'] = array(
'#value' => uc_price($node->list_price, $context, $options),
'#cell_attributes' => array(
'nowrap' => 'nowrap',
),
);
}
if ($enabled['sell_price']) {
$context['class'][1] = 'sell';
$context['field'] = 'sell_price';
$data['price'] = array(
'#value' => uc_price($node->sell_price, $context, $options),
'#cell_attributes' => array(
'nowrap' => 'nowrap',
),
);
}
if (module_exists('uc_cart') && arg(0) != 'admin' && $enabled['add_to_cart']) {
$data['add_to_cart'] = array(
'#value' => drupal_get_form('uc_catalog_buy_it_now_form_' . $node->nid, $node),
);
}
$table[] = $data;
}
if (!count(element_children($table))) {
$table[] = array(
'name' => array(
'#value' => t('No products available.'),
'#cell_attributes' => array(
'colspan' => 'full',
),
),
);
}
return $table;
}
/**
* Form builder for uc_catalog_buy_it_now_form().
*
* @see uc_product_forms()
* @see uc_catalog_buy_it_now_form_validate()
* @see uc_catalog_buy_it_now_form_submit()
* @ingroup forms
*/
function uc_catalog_buy_it_now_form($form_state, $node) {
$form = array();
$form['#validate'][] = 'uc_catalog_buy_it_now_form_validate';
$form['#submit'][] = 'uc_catalog_buy_it_now_form_submit';
$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',
),
);
uc_form_alter($form, $form_state, __FUNCTION__);
return $form;
}
/**
* Redirects to the product page if attributes need to be selected.
*
* @see uc_catalog_buy_it_now_form()
* @see uc_catalog_buy_it_now_form_submit()
*/
function uc_catalog_buy_it_now_form_validate($form, &$form_state) {
if (module_exists('uc_attribute')) {
$attributes = uc_product_get_attributes($form_state['values']['nid']);
if (!empty($attributes)) {
drupal_set_message(t('This product has options that need to be selected before purchase. Please select them in the form below.'), 'error');
drupal_goto('node/' . $form_state['values']['nid']);
}
}
}
/**
* Form submission handler for uc_catalog_buy_it_now_form().
*
* @see uc_catalog_buy_it_now_form()
* @see uc_catalog_buy_it_now_form_validate()
*/
function uc_catalog_buy_it_now_form_submit($form, &$form_state) {
$form_state['redirect'] = uc_cart_add_item($form_state['values']['nid'], 1, module_invoke_all('add_to_cart_data', $form_state['values']), NULL, variable_get('uc_cart_add_item_msg', TRUE));
}
/**
* Formats a product's model number.
*
* @ingroup themeable
*/
function theme_uc_product_model($model, $teaser = 0, $page = 0) {
$output = '<div class="product-info model">';
$output .= t('SKU: @sku', array(
'@sku' => $model,
));
$output .= '</div>';
return $output;
}
/**
* Formats a product's body.
*
* @ingroup themeable
*/
function theme_uc_product_body($body, $teaser = 0, $page = 0) {
$output = '<div class="product-body">';
$output .= $body;
$output .= '</div>';
return $output;
}
/**
* Wraps 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.
*
* @param $node
* A product node.
*
* @see uc_product_forms()
* @see uc_product_add_to_cart_form_submit()
* @ingroup forms
*/
function uc_product_add_to_cart_form($form_state, $node) {
$form = array();
$form['#validate'][] = 'uc_product_add_to_cart_form_validate';
$form['#submit'][] = 'uc_product_add_to_cart_form_submit';
$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' => 'uc_quantity',
'#title' => t('Quantity'),
'#default_value' => $node->default_qty,
);
}
else {
$form['qty'] = array(
'#type' => 'hidden',
'#value' => $node->default_qty ? $node->default_qty : 1,
);
}
$form['submit'] = array(
'#type' => 'submit',
'#value' => variable_get('uc_product_add_to_cart_text', t('Add to cart')),
'#id' => 'edit-submit-' . $node->nid,
'#attributes' => array(
'class' => 'node-add-to-cart',
),
);
$form['node'] = array(
'#type' => 'value',
'#value' => $node,
);
uc_form_alter($form, $form_state, __FUNCTION__);
return $form;
}
/**
* Form submission handler for uc_product_add_to_cart_form().
*
* @see uc_product_add_to_cart_form()
*/
function uc_product_add_to_cart_form_submit($form, &$form_state) {
$form_state['redirect'] = uc_cart_add_item($form_state['values']['nid'], $form_state['values']['qty'], module_invoke_all('add_to_cart_data', $form_state['values']), NULL, variable_get('uc_cart_add_item_msg', TRUE));
}
/**
* Formats a product's price.
*
* This is an extra wrapper theme around the output of uc_price() when it is
* used in the product body. For expedience, it takes the same parameters as
* uc_price().
*
* @param $price
* The monetary amount.
* @param $context
* Determines the CSS class of the <div>, and helps determine if the price
* needs to be altered.
* @param $options
* Toggles the label and other formatting.
*
* @see uc_price()
* @ingroup themeable
*/
function theme_uc_product_price($price, $context, $options = array()) {
$output = '<div class="product-info ' . implode(' ', (array) $context['class']) . '">';
$output .= uc_price($price, $context, $options);
$output .= '</div>';
return $output;
}
/**
* Formats a product's weight.
*
* @ingroup themeable
*/
function theme_uc_product_weight($weight, $unit = NULL, $teaser = 0, $page = 0) {
$output = '<div class="product-info weight">';
$output .= t('Weight: !weight', array(
'!weight' => uc_weight_format($weight, $unit),
));
$output .= '</div>';
return $output;
}
/**
* Formats 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="product-info 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;
}
/**
* Formats a product's images.
*
* @ingroup themeable
*/
function theme_uc_product_image($images, $teaser = 0, $page = 0) {
static $rel_count = 0;
// Get the current product image widget.
$image_widget = uc_product_get_image_widget();
$first = array_shift($images);
$output = '<div class="product-image"><div class="main-product-image">';
$output .= '<a href="' . imagecache_create_url('product_full', $first['filepath']) . '" title="' . $first['data']['title'] . '"';
if ($image_widget) {
$image_widget_func = $image_widget['callback'];
$output .= $image_widget_func($rel_count);
}
$output .= '>';
$output .= theme('imagecache', 'product', $first['filepath'], $first['data']['alt'], $first['data']['title']);
$output .= '</a></div><div class="more-product-images">';
foreach ($images as $thumbnail) {
// Node preview adds extra values to $images that aren't files.
if (!is_array($thumbnail) || empty($thumbnail['filepath'])) {
continue;
}
$output .= '<a href="' . imagecache_create_url('product_full', $thumbnail['filepath']) . '" title="' . $thumbnail['data']['title'] . '"';
if ($image_widget) {
$output .= $image_widget_func($rel_count);
}
$output .= '>';
$output .= theme('imagecache', 'uc_thumbnail', $thumbnail['filepath'], $thumbnail['data']['alt'], $thumbnail['data']['title']);
$output .= '</a>';
}
$output .= '</div></div>';
$rel_count++;
return $output;
}
/**
* Returns an array of product node types.
*/
function uc_product_types() {
return module_invoke_all('product_types');
}
/**
* Returns an associative array of product node type names keyed by ID.
*/
function uc_product_type_names() {
$names = array();
// Get all the node meta data.
$node_info = module_invoke_all('node_info');
// Loop through each product node type.
foreach (uc_product_types() as $type) {
$names[$type] = $node_info[$type]['name'];
}
return $names;
}
/**
* Determines whether or not a given node or node type is a product.
*
* @param $node
* Either a full node object/array, a node ID, or a node type.
*
* @return
* TRUE or FALSE indicating whether or not a node type is a product node type.
*/
function uc_product_is_product($node) {
// Load the node object if we received an integer as an argument.
if (is_numeric($node)) {
$node = node_load($node);
}
// Determine the node type based on the data type of $node.
if (is_object($node)) {
$type = $node->type;
}
elseif (is_array($node)) {
$type = $node['type'];
}
elseif (is_string($node)) {
$type = $node;
}
// If no node type was found, go ahead and return FALSE.
if (!$type) {
return FALSE;
}
// Return TRUE or FALSE depending on whether or not the node type is in the
// product types array.
return in_array($type, uc_product_types());
}
/**
* Determines whether or not a given form array is a product node form.
*
* @param $form
* The form array to examine.
*
* @return
* TRUE or FALSE indicating whether or not the form is a product node form.
*/
function uc_product_is_product_form($form) {
return $form['#id'] == 'node-form' && uc_product_is_product($form['#node']);
}
/**
* Gets all models of a product (node).
*
* Gathers any modules' models on this node, then add the node's SKU and the
* optional 'Any' option.
*
* @param $nid
* The node ID of the product.
* @param $add_blank
* String to use for the initial blank entry. If not desired, set to NULL
* or FALSE. Make sure to localize the string first. Defaults to '- Any -'.
*
* @return
* An associative array of model numbers. The key for '- Any -' is the empty
* string.
*/
function uc_product_get_models($nid, $add_blank = TRUE) {
// Keep backward compatibility with previous versions of 6.x-2.x, where
// first argument was a product object.
if (is_object($nid)) {
$nid = $nid->nid;
}
// Get any modules' SKUs on this node.
$models = module_invoke_all('uc_product_models', $nid);
// Add the base SKU of the node.
$models[] = db_result(db_query("SELECT model FROM {uc_products} WHERE nid = %d", $nid));
// Now we map the SKUs to the keys, for form handling, etc.
$models = drupal_map_assoc($models);
// Sort the SKUs
asort($models);
// And finally, we prepend 'Any' so it's the first option.
if (!empty($add_blank) || $add_blank === '') {
if ($add_blank === TRUE) {
$add_blank = t('- Any -');
}
return array(
'' => $add_blank,
) + $models;
}
return $models;
}
/**
* Gets 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;
}
/**
* Returns an HTML img tag based on a node's attached image.
*
* @param $node_id
* The node's id.
* @param $format
* The imagecache preset used to format the image. 'product' by default.
*
* @return
* An HTML img. When $format is not 'product', the image is a link to the
* 'product_full' preset if a JavaScript display widget is available
* (Colorbox, Thickbox, and Lightbox2 are possible). For other values
* of $format, the image links to the node page.
*/
function uc_product_get_picture($node_id, $format = 'product') {
$output = '';
$product = node_load($node_id);
if (!module_exists('imagecache') || !($field = variable_get('uc_image_' . $product->type, ''))) {
return '';
}
// Get the current product image widget.
$image_widget = uc_product_get_image_widget();
if (isset($product->{$field})) {
$image = $product->{$field}[0];
$path = $image['filepath'];
if (file_exists($path)) {
$img = theme('imagecache', $format, $path, $image['data']['alt'], $image['data']['title']);
if ($format == 'product') {
if ($image_widget) {
$image_widget_func = $image_widget['callback'];
$output .= '<a title="' . $image['title'] . '" href="' . imagecache_create_url('product_full', $path) . '" ' . $image_widget_func(NULL) . '>';
}
$output .= $img;
if ($image_widget) {
$output .= '</a>';
}
}
else {
$output = l($img, 'node/' . $product->nid, array(
'html' => TRUE,
));
}
}
}
return $output;
}
/**
* Finds the JavaScript image display module to use on product pages.
*/
function uc_product_get_image_widget() {
static $got_widget = FALSE, $image_widget = array();
// Get the current image widget, if any.
if (!$got_widget) {
// Invoke hook to find widgets.
$image_widgets = uc_store_get_image_widgets();
// Find widget preference, if any.
$widget_name = variable_get('uc_product_image_widget', NULL);
if ($widget_name == 'none') {
// Don't use any image widgets.
}
elseif ($widget_name != NULL) {
// Widget to use has been set in admin menu.
$image_widget = $image_widgets[$widget_name];
}
else {
// Widget to use has not been chosen, so default to first element of
// array, if any.
$keys = array_keys($image_widgets);
if ($keys) {
$image_widget = $image_widgets[$keys[0]];
variable_set('uc_product_image_widget', $keys[0]);
}
}
$got_widget = TRUE;
}
return $image_widget;
}
/**
* Returns HTML for the product description.
*
* Modules adding information use hook_product_description() and modules
* wanting to alter the output before rendering can do so by implementing
* hook_product_description_alter(). By default, all descriptions supplied by
* modules via hook_product_description() are concatenated together.
*
* NOTE: hook_product_description() supercedes the deprecated
* hook_cart_item_description().
*
* @param $product
* Product object.
*
* @return
* HTML rendered product description.
*/
function uc_product_get_description($product) {
// Run through implementations of hook_product_description()
$description = module_invoke_all('product_description', $product);
// Now allow alterations via hook_product_description_alter()
drupal_alter('product_description', $description, $product);
return drupal_render($description);
}
/**
* Loads a product class, or all classes.
*/
function uc_product_class_load($class_id = NULL, $reset = FALSE) {
static $classes;
if ($reset || !isset($classes)) {
// Load classes from database.
$classes = array();
$result = db_query("SELECT * FROM {uc_product_classes}");
while ($class = db_fetch_object($result)) {
$classes[$class->pcid] = $class;
}
// Merge any module-defined classes.
foreach (module_invoke_all('uc_product_default_classes') as $pcid => $class) {
// The default class can be overridden by a module if needed,
// but is not treated as a real class elsewhere.
if ($pcid == 'product') {
continue;
}
$class += array(
'pcid' => $pcid,
'name' => $pcid,
'description' => '',
);
if (isset($classes[$pcid])) {
// Merge defaults with the existing database info.
$classes[$pcid] = (object) array_merge($class, (array) $classes[$pcid]);
}
else {
// Ensure the class info is saved in the database.
drupal_write_record('uc_product_classes', $class);
$classes[$pcid] = (object) $class;
}
// Module-defined classes cannot be deleted.
$classes[$pcid]->locked = TRUE;
}
}
return is_null($class_id) ? $classes : $classes[$class_id];
}
/**
* Returns data for a product feature, 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;
}
/**
* Saves a product feature to a product node.
*
* @param $data
* An array consisting of the following keys:
* - pfid: (optional) When editing an existing product feature, the numeric
* ID of the feature.
* - nid: The numeric ID of the product node.
* - fid: The string ID of the feature type.
* - description: The string describing 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));
}
}
if (!empty($data['pfid']) && db_result(db_query("SELECT COUNT(*) FROM {uc_product_features} WHERE pfid = %d", intval($data['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']));
drupal_set_message(t('The product feature has been updated.'));
}
else {
// Otherwise insert this feature as a new row.
db_query("INSERT INTO {uc_product_features} (nid, fid, description) VALUES (%d, '%s', '%s')", $data['nid'], $data['fid'], $data['description']);
drupal_set_message(t('The product feature has been added.'));
}
return 'node/' . $data['nid'] . '/edit/features';
}
/**
* Loads all product feature for a node.
*
* @param $nid
* The product node ID.
*
* @return
* The array of all product features object.
*/
function uc_product_feature_load_multiple($nid) {
$result = db_query("SELECT * FROM {uc_product_features} WHERE nid = %d ORDER BY pfid ASC", $nid);
$features = array();
while ($feature = db_fetch_object($result)) {
$features[$feature->pfid] = $feature;
}
return $features;
}
/**
* Loads a product feature object.
*
* @todo: should return an object instead of array.
*
* @param $pfid
* The product feature ID.
* @param $fid
* Optional. Specify a specific feature id.
*
* @return
* The product feature object.
*/
function uc_product_feature_load($pfid, $fid = NULL) {
if (isset($fid)) {
$feature = db_fetch_array(db_query("SELECT * FROM {uc_product_features} WHERE pfid = %d AND fid = '%s'", $pfid, $fid));
}
else {
$feature = db_fetch_array(db_query("SELECT * FROM {uc_product_features} WHERE pfid = %d", $pfid));
}
return $feature;
}
/**
* Deletes a product feature object.
*
* @param $pfid
* The product feature ID.
*
* @return
* The product feature object.
*/
function uc_product_feature_delete($pfid) {
$feature = uc_product_feature_load($pfid);
// Call the delete function for this product feature if it exists.
$func = uc_product_feature_data($feature['fid'], 'delete');
if (function_exists($func)) {
$func($pfid);
}
db_query("DELETE FROM {uc_product_features} WHERE pfid = %d", $pfid);
return SAVED_DELETED;
}
/**
* Creates a file field with an image field widget, and attach it to products.
*
* This field is used by default on the product page, as well as on the cart
* and catalog pages to represent the products they list. Instances are added
* to new product classes, and other node types that claim product-ness should
* call this function for themselves.
*
* @param $type
* The content type to which the image field is to be attached. This may be a
* a single type as a string, or an array of types. If NULL, all product
* types get an instance of the field.
*/
function uc_product_add_default_image_field($type = NULL) {
module_load_include('inc', 'imagefield', 'imagefield_widget');
module_load_include('inc', 'filefield', 'filefield_widget');
module_load_include('inc', 'content', 'includes/content.crud');
$label = t('Image');
$field = array(
'label' => $label,
'type' => 'filefield',
'widget_type' => 'imagefield_widget',
'weight' => -2,
'file_extensions' => 'gif jpg png',
'custom_alt' => 1,
'custom_title' => 1,
'description' => '',
'required' => 0,
'multiple' => '1',
'list_field' => '0',
'list_default' => 1,
'description_field' => '0',
'module' => 'filefield',
'widget_module' => 'imagefield',
'columns' => array(
'fid' => array(
'type' => 'int',
'not null' => FALSE,
),
'list' => array(
'type' => 'int',
'size' => 'tiny',
'not null' => FALSE,
),
'data' => array(
'type' => 'text',
'serialize' => TRUE,
),
),
'display_settings' => array(
'label' => array(
'format' => 'hidden',
),
'teaser' => array(
'format' => 'hidden',
),
'full' => array(
'format' => 'hidden',
),
4 => array(
'format' => 'hidden',
),
),
);
if (module_exists('imagefield_tokens')) {
$field['alt'] = '[title]';
$field['title'] = '[title]';
}
if ($type) {
// Accept single or multiple types as input.
$types = (array) $type;
}
else {
$types = uc_product_types();
}
foreach ($types as $type) {
$field_name = variable_get('uc_image_' . $type, '');
if (empty($field_name)) {
$field_name = 'field_image_cache';
}
// Only add the field if it doesn't exist. Don't overwrite any changes.
$instances = content_field_instance_read(array(
'field_name' => $field_name,
'type_name' => $type,
));
if (count($instances) < 1) {
$prior_instances = content_field_instance_read(array(
'field_name' => $field_name,
));
if ($prior_instances) {
// Copy the prior instance.
content_field_instance_create(array(
'field_name' => $field_name,
'type_name' => $type,
));
}
else {
// Create a new field and instance.
$field['field_name'] = $field_name;
$field['type_name'] = $type;
content_field_instance_create($field);
}
variable_set('uc_image_' . $type, $field_name);
}
}
}
/**
* Implements hook_imagecache_default_presets().
*/
function uc_product_imagecache_default_presets() {
$presets = array();
$presets['product'] = array(
'presetname' => 'product',
'actions' => array(
array(
'weight' => '0',
'module' => 'uc_product',
'action' => 'imagecache_scale',
'data' => array(
'width' => '250',
'height' => '250',
'upscale' => 0,
),
),
),
);
$presets['product_full'] = array(
'presetname' => 'product_full',
'actions' => array(),
);
$presets['product_list'] = array(
'presetname' => 'product_list',
'actions' => array(
array(
'weight' => '0',
'module' => 'uc_product',
'action' => 'imagecache_scale',
'data' => array(
'width' => '100',
'height' => '100',
'upscale' => 0,
),
),
),
);
$presets['uc_thumbnail'] = array(
'presetname' => 'uc_thumbnail',
'actions' => array(
array(
'weight' => '0',
'module' => 'uc_product',
'action' => 'imagecache_scale',
'data' => array(
'width' => '35',
'height' => '35',
'upscale' => 0,
),
),
),
);
return $presets;
}
/**
* Implements hook_views_api().
*/
function uc_product_views_api() {
return array(
'api' => '2.0',
'path' => drupal_get_path('module', 'uc_product') . '/views',
);
}
/**
* Implements hook_features_api().
*/
function uc_product_features_api() {
return array(
'uc_product_classes' => array(
'name' => t('Ubercart product classes'),
'feature_source' => TRUE,
'default_hook' => 'uc_product_default_classes',
'file' => drupal_get_path('module', 'uc_product') . '/uc_product.features.inc',
),
);
}
Functions
Name | Description |
---|---|
theme_uc_product_add_to_cart | Wraps the "Add to Cart" form in a <div>. |
theme_uc_product_body | Formats a product's body. |
theme_uc_product_dimensions | Formats a product's length, width, and height. |
theme_uc_product_dimensions_form | Puts length, width, and height fields on the same line. |
theme_uc_product_form_prices | Formats product price fields for node/%/edit form. |
theme_uc_product_form_weight | Formats product weight fields for node/%/edit form. |
theme_uc_product_image | Formats a product's images. |
theme_uc_product_model | Formats a product's model number. |
theme_uc_product_price | Formats a product's price. |
theme_uc_product_weight | Formats a product's weight. |
uc_catalog_buy_it_now_form | Form builder for uc_catalog_buy_it_now_form(). |
uc_catalog_buy_it_now_form_submit | Form submission handler for uc_catalog_buy_it_now_form(). |
uc_catalog_buy_it_now_form_validate | Redirects to the product page if attributes need to be selected. |
uc_product_access | Implements hook_access(). |
uc_product_add_default_image_field | Creates a file field with an image field widget, and attach it to products. |
uc_product_add_to_cart_data | Implements 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 | Form submission handler for uc_product_add_to_cart_form(). |
uc_product_cart_display | Implements hook_cart_display(). |
uc_product_class_delete_access | Menu access callback for deleting product classes. |
uc_product_class_load | Loads a product class, or all classes. |
uc_product_content_extra_fields | Implements hook_content_extra_fields(). |
uc_product_content_extra_fields_alter | Implements hook_content_extra_fields_alter(). |
uc_product_content_fieldapi | Implements hook_content_fieldapi(). |
uc_product_delete | Implements hook_delete(). |
uc_product_edit_access | Menu access callback for 'node/%node/edit/product'. |
uc_product_enable | Implements hook_enable(). |
uc_product_features_api | Implements hook_features_api(). |
uc_product_feature_access | Menu access callback for 'node/%node/edit/features'. |
uc_product_feature_data | Returns data for a product feature, based on the feature ID and array key. |
uc_product_feature_delete | Deletes a product feature object. |
uc_product_feature_form | Returns a form array with some default hidden values and submit button. |
uc_product_feature_load | Loads a product feature object. |
uc_product_feature_load_multiple | Loads all product feature for a node. |
uc_product_feature_save | Saves a product feature to a product node. |
uc_product_field_enabled | Convenience function to get the enabled fields. |
uc_product_form | Implements hook_form(). |
uc_product_forms | Implements hook_forms(). |
uc_product_form_alter | Implements hook_form_alter(). |
uc_product_form_node_type_form_alter | Implements hook_form_{$form_id}_alter(). |
uc_product_get_cost | Gets the cost of a product node. |
uc_product_get_description | Returns HTML for the product description. |
uc_product_get_image_widget | Finds the JavaScript image display module to use on product pages. |
uc_product_get_models | Gets all models of a product (node). |
uc_product_get_picture | Returns an HTML img tag based on a node's attached image. |
uc_product_help | Implements hook_help(). |
uc_product_i18nsync_fields_alter | Implements hook_i18nsync_fields_alter(). |
uc_product_imagecache_default_presets | Implements hook_imagecache_default_presets(). |
uc_product_init | Implements hook_init(). |
uc_product_insert | Implements hook_insert(). |
uc_product_is_product | Determines whether or not a given node or node type is a product. |
uc_product_is_product_form | Determines whether or not a given form array is a product node form. |
uc_product_load | Implements hook_load(). |
uc_product_menu | Implements hook_menu(). |
uc_product_node_info | Implements hook_node_info(). |
uc_product_node_type | Implements hook_node_type(). |
uc_product_perm | Implements hook_perm(). |
uc_product_preprocess_node | Implements hook_preprocess_node(). |
uc_product_product_class | Implements hook_product_class(). |
uc_product_product_types | Implements hook_product_types(). |
uc_product_save_continue_submit | After the node is saved, redirects to the edit page. |
uc_product_store_status | Implements hook_store_status(). |
uc_product_table | Displays product fields in a TAPIr table. |
uc_product_table_header | Returns the table header for the product view table. |
uc_product_theme | Implements hook_theme(). |
uc_product_token_list | Implements hook_token_list(). |
uc_product_token_values | Implements hook_token_values(). |
uc_product_types | Returns an array of product node types. |
uc_product_type_names | Returns an associative array of product node type names keyed by ID. |
uc_product_update | Implements hook_update(). |
uc_product_update_cart_item | Implements hook_update_cart_item(). |
uc_product_validate | Implements hook_validate(). |
uc_product_view | Implements hook_view(). |
uc_product_views_api | Implements hook_views_api(). |