add attributes form. You may then adjust the settings for these attributes on this page and go on to configure their options in the Options tab.', array('!url' => url('node/'. $arg[1] .'/edit/uc-attributes/add'))); case 'admin/store/products/classes/%/attributes': return t('Add attributes to the product class using the add attributes form. You may then adjust the settings for these attributes on this page and go on to configure their options in the Options tab.', array('!url' => url('admin/store/products/classes/'. $arg[4] .'/attributes/add'))); // Help message for adding an attribute to a product or class. case 'node/%/edit/uc-attributes/add': case 'admin/store/products/classes/%/attributes/add': return t('Select the attributes you want to add and submit the form.'); // Help message for adjusting the options on a product or class. case 'node/%/edit/uc-options': case 'admin/store/products/classes/%/options': return t('Use the checkboxes to enable options for attributes and the radio buttons to specify defaults for the enabled options. Use the other fields to override the default settings for each option. Attributes with no enabled options will be displayed as text fields.'); // Help message for the product Adjustments tab. case 'node/%/edit/uc-adjustments': return t('Enter an alternate SKU to be used when the specified set of options are chosen and the product is added to the cart. Warning: Adding or removing attributes from this product will reset all the SKUs on this page to the default product SKU.'); } } /** * Implementation of hook_menu(). */ function uc_attribute_menu() { $items['admin/store/attributes'] = array( 'title' => 'Attributes', 'description' => 'Create and edit attributes and options.', 'page callback' => 'uc_attribute_admin', 'access arguments' => array('administer attributes'), 'type' => MENU_NORMAL_ITEM, 'weight' => -1, 'file' => 'uc_attribute.admin.inc', ); $items['admin/store/attributes/overview'] = array( 'title' => 'Overview', 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => 0, ); $items['admin/store/attributes/add'] = array( 'title' => 'Add an attribute', 'page callback' => 'drupal_get_form', 'page arguments' => array('uc_attribute_form'), 'access arguments' => array('administer attributes'), 'type' => MENU_LOCAL_TASK, 'weight' => 5, 'file' => 'uc_attribute.admin.inc', ); $items['admin/store/settings/attributes'] = array( 'title' => 'Attribute settings', 'description' => 'Configure the attribute settings', 'page callback' => 'drupal_get_form', 'page arguments' => array('uc_attribute_admin_settings'), 'access arguments' => array('administer attributes'), 'type' => MENU_NORMAL_ITEM, 'file' => 'uc_attribute.admin.inc', ); $items['admin/store/attributes/%uc_attribute/edit'] = array( 'title' => 'Edit attribute', 'page callback' => 'drupal_get_form', 'page arguments' => array('uc_attribute_form', 3), 'access arguments' => array('administer attributes'), 'type' => MENU_CALLBACK, 'file' => 'uc_attribute.admin.inc', ); $items['admin/store/attributes/%uc_attribute/delete'] = array( 'page callback' => 'drupal_get_form', 'page arguments' => array('uc_attribute_delete_confirm', 3), 'access arguments' => array('administer attributes'), 'type' => MENU_CALLBACK, 'file' => 'uc_attribute.admin.inc', ); $items['admin/store/attributes/%uc_attribute/options'] = array( 'title' => 'Options', 'page callback' => 'drupal_get_form', 'page arguments' => array('uc_attribute_options_form', 3), 'access arguments' => array('administer attributes'), 'type' => MENU_CALLBACK, 'file' => 'uc_attribute.admin.inc', ); $items['admin/store/attributes/%uc_attribute/options/overview'] = array( 'title' => 'Overview', 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => 0, 'file' => 'uc_attribute.admin.inc', ); $items['admin/store/attributes/%uc_attribute/options/add'] = array( 'title' => 'Add an option', 'page callback' => 'drupal_get_form', 'page arguments' => array('uc_attribute_option_form', 3, NULL), 'access arguments' => array('administer attributes'), 'type' => MENU_LOCAL_TASK, 'weight' => 5, 'file' => 'uc_attribute.admin.inc', ); $items['admin/store/attributes/%uc_attribute/options/%uc_attribute_option/edit'] = array( 'title' => 'Edit option', 'page callback' => 'drupal_get_form', 'page arguments' => array('uc_attribute_option_form', 3, 5), 'access arguments' => array('administer attributes'), 'type' => MENU_CALLBACK, 'file' => 'uc_attribute.admin.inc', ); $items['admin/store/attributes/%uc_attribute/options/%uc_attribute_option/delete'] = array( 'title' => 'Delete option', 'page callback' => 'drupal_get_form', 'page arguments' => array('uc_attribute_option_delete_confirm', 3, 5), 'access arguments' => array('administer attributes'), 'type' => MENU_CALLBACK, 'file' => 'uc_attribute.admin.inc', ); // Menu items for default product class attributes and options. $items['admin/store/products/classes/%uc_product_class/attributes'] = array( 'title' => 'Attributes', 'page callback' => 'drupal_get_form', 'page arguments' => array('uc_object_attributes_form', 4, 'class'), 'access callback' => 'uc_attribute_product_class_access', 'type' => MENU_LOCAL_TASK, 'weight' => 1, 'file' => 'uc_attribute.admin.inc', ); $items['admin/store/products/classes/%uc_product_class/options'] = array( 'title' => 'Options', 'page callback' => 'drupal_get_form', 'page arguments' => array('uc_object_options_form', 4, 'class'), 'access callback' => 'uc_attribute_product_class_access', 'type' => MENU_LOCAL_TASK, 'weight' => 2, 'file' => 'uc_attribute.admin.inc', ); // Insert subitems into the edit node page for product types. $items['node/%node/edit/uc-attributes'] = array( 'title' => 'Attributes', 'page callback' => 'drupal_get_form', 'page arguments' => array('uc_object_attributes_form', 1, 'product', 'overview'), 'access callback' => 'uc_attribute_product_access', 'access arguments' => array(1), 'type' => MENU_LOCAL_TASK, 'weight' => 1, 'file' => 'uc_attribute.admin.inc', ); $items['node/%node/edit/uc-attributes/add'] = array( 'title' => 'Attributes', 'page callback' => 'drupal_get_form', 'page arguments' => array('uc_object_attributes_form', 1, 'product', 'add'), 'access callback' => 'uc_attribute_product_access', 'access arguments' => array(1), 'type' => MENU_LOCAL_TASK, 'weight' => 1, 'file' => 'uc_attribute.admin.inc', ); $items['node/%node/edit/uc-options'] = array( 'title' => 'Options', 'page callback' => 'drupal_get_form', 'page arguments' => array('uc_object_options_form', 1, 'product'), 'access callback' => 'uc_attribute_product_option_access', 'access arguments' => array(1), 'type' => MENU_LOCAL_TASK, 'weight' => 2, 'file' => 'uc_attribute.admin.inc', ); $items['node/%node/edit/uc-adjustments'] = array( 'title' => 'Adjustments', 'page callback' => 'drupal_get_form', 'page arguments' => array('uc_product_adjustments_form', 1), 'access callback' => 'uc_attribute_product_option_access', 'access arguments' => array(1), 'type' => MENU_LOCAL_TASK, 'weight' => 3, 'file' => 'uc_attribute.admin.inc', ); return $items; } function uc_attribute_product_class_access() { return user_access('administer product classes') && user_access('administer attributes'); } function uc_attribute_product_access($node) { if ($node->type == 'product_kit') { return FALSE; } return uc_product_is_product($node) && node_access('update', $node) && (user_access('administer attributes') || user_access('administer product attributes')); } function uc_attribute_product_option_access($node) { if ($node->type == 'product_kit') { return FALSE; } return uc_product_is_product($node) && node_access('update', $node) && (user_access('administer attributes') || user_access('administer product attributes') || user_access('administer product options')); } function uc_attribute_perm() { return array('administer attributes', 'administer product attributes', 'administer product options'); } function uc_attribute_init() { drupal_add_css(drupal_get_path('module', 'uc_attribute') .'/uc_attribute.css'); } /* * Implementation of hook_theme(). */ function uc_attribute_theme() { return array( 'uc_attribute_add_to_cart' => array( 'arguments' => array('form' => NULL), ), 'uc_object_attributes_form' => array( 'arguments' => array('form' => NULL), 'file' => 'uc_attribute.admin.inc', ), 'uc_object_options_form' => array( 'arguments' => array('form' => NULL), 'file' => 'uc_attribute.admin.inc', ), 'uc_attribute_options_form' => array( 'arguments' => array('form' => NULL), 'file' => 'uc_attribute.admin.inc', ), ); } /** * Implementation of hook_form_alter(). * * Attach option selectors to the form with the "Add to Cart" button. */ function uc_attribute_form_alter(&$form, &$form_state, $form_id) { if (strpos($form_id, 'add_to_cart_form') || strpos($form_id, 'add_product_form')) { // If the node has a product list, add attributes to them if (isset($form['products']) && count(element_children($form['products']))) { foreach (element_children($form['products']) as $key) { $form['products'][$key]['attributes'] = _uc_attribute_alter_form(node_load($key)); if (is_array($form['products'][$key]['attributes'])) { $form['products'][$key]['attributes']['#tree'] = TRUE; $form['products'][$key]['#type'] = 'fieldset'; } } } // If not, add attributes to the node. else { if (strpos($form_id, 'add_product_form')) { $form['attributes'] = _uc_attribute_alter_form(node_load($form['#parameters'][3])); } else { $form['attributes'] = _uc_attribute_alter_form($form['#parameters'][2]); } if (is_array($form['attributes'])) { $form['attributes']['#tree'] = TRUE; $form['attributes']['#weight'] = -1; } } } } /** * Implementation of hook_nodeapi(). */ function uc_attribute_nodeapi(&$node, $op, $arg3 = NULL, $arg4 = NULL) { if (uc_product_is_product($node->type)) { switch ($op) { case 'load': $attributes = uc_product_get_attributes($node->nid); if (is_array($attributes) && !empty($attributes)) { $node->attributes = $attributes; return array('attributes' => $attributes); } break; case 'insert': switch ($GLOBALS['db_type']) { case 'mysqli': case 'mysql': db_query("INSERT IGNORE INTO {uc_product_attributes} (nid, aid, ordering, required, display, default_option) SELECT %d, aid, ordering, required, display, default_option FROM {uc_class_attributes} WHERE pcid = '%s'", $node->nid, $node->type); db_query("INSERT IGNORE INTO {uc_product_options} (nid, oid, cost, price, weight, ordering) SELECT %d, oid, cost, price, weight, ordering FROM {uc_class_attribute_options} WHERE pcid = '%s'", $node->nid, $node->type); break; case 'pgsql': db_query("INSERT INTO {uc_product_attributes} (nid, aid, ordering, required, display, default_option) SELECT %d, aid, ordering, required, display, default_option FROM {uc_class_attributes} WHERE pcid = '%s'", $node->nid, $node->type); db_query("INSERT INTO {uc_product_options} (nid, oid, cost, price, weight, ordering) SELECT %d, oid, cost, price, weight, ordering FROM {uc_class_attribute_options} WHERE pcid = '%s'", $node->nid, $node->type); break; } break; case 'delete': db_query("DELETE FROM {uc_product_options} WHERE nid = %d", $node->nid); db_query("DELETE FROM {uc_product_adjustments} WHERE nid = %d", $node->nid); db_query("DELETE FROM {uc_product_attributes} WHERE nid = %d", $node->nid); break; case 'update index': $output = ''; $attributes = uc_product_get_attributes($node->nid); foreach ($attributes as $attribute) { $output .= '
'. print_r($form_state['values']['attributes'], TRUE) . $model .''); // Preserve the 'attributes' key to allow other modules to add to the data field. return array('attributes' => $form_values['attributes'], 'model' => $model); } function uc_attribute_order_product_alter(&$product, $order) { // Convert the attribute and option ids to their current names. This // preserves the important data in case the attributes or options are // changed later. if (is_array($product->data['attributes']) && is_numeric(key($product->data['attributes']))) { $attributes = array(); $options = _uc_cart_product_get_options($product); foreach ($options as $aid => $option) { $attributes[$option['attribute']] = $option['name']; } $product->data['attributes'] = $attributes; } } /** * Implementation of hook_product_class(). */ function uc_attribute_product_class($type, $op) { switch ($op) { case 'delete': db_query("DELETE FROM {uc_class_attributes} WHERE pcid = '%s'", $type); db_query("DELETE FROM {uc_class_attribute_options} WHERE pcid = '%s'", $type); break; } } /** * Implementation of hook_cart_item(). */ function uc_attribute_cart_item($op, &$item) { switch ($op) { case 'load': $options = _uc_cart_product_get_options($item); $op_costs = 0; $op_prices = 0; $op_weight = 0; foreach ($options as $option) { $op_costs += $option['cost']; $op_prices += $option['price']; $op_weight += $option['weight']; } $item->cost += $op_costs; $item->price += $op_prices; $item->weight += $op_weight; if (!empty($item->data['model'])) { $item->model = $item->data['model']; } break; } } function uc_attribute_cart_item_description($item) { $rows = array(); foreach (_uc_cart_product_get_options($item) as $option) { $rows[] = t('@attribute: @option', array('@attribute' => $option['attribute'], '@option' => $option['name'])); } if (count($rows)) { $output = theme('item_list', $rows, NULL, 'ul', array('class' => 'product-description')); } return $output; } /****************************************************************************** * Module Functions * ******************************************************************************/ /** * Load an attribute from the database. * * @param $attr_id * The id of the attribute. * @param $nid * Node id. If given, the attribute will have the options that have been * assigned to that node for the attribute. */ function uc_attribute_load($attr_id, $nid = NULL, $type = '') { if ($nid) { switch ($type) { case 'product': $attribute = db_fetch_object(db_query("SELECT a.aid, a.name, a.label AS default_label, a.ordering AS default_ordering, a.required AS default_required, a.display AS default_display, a.description, pa.label, pa.default_option, pa.required, pa.ordering, pa.display FROM {uc_attributes} AS a LEFT JOIN {uc_product_attributes} AS pa ON a.aid = pa.aid AND pa.nid = %d WHERE a.aid = %d", $nid, $attr_id)); $result = db_query("SELECT po.nid, po.oid, po.cost, po.price, po.weight, po.ordering, ao.name, ao.aid FROM {uc_product_options} AS po LEFT JOIN {uc_attribute_options} AS ao ON po.oid = ao.oid AND nid = %d WHERE aid = %d ORDER BY po.ordering, ao.name", $nid, $attr_id); break; case 'class': $attribute = db_fetch_object(db_query("SELECT a.aid, a.name, a.label AS default_label, a.ordering AS default_ordering, a.required AS default_required, a.display AS default_display, a.description, ca.default_option, ca.label, ca.required, ca.ordering, ca.display FROM {uc_attributes} AS a LEFT JOIN {uc_class_attributes} AS ca ON a.aid = ca.aid AND ca.pcid = '%s' WHERE a.aid = %d", $nid, $attr_id)); $result = db_query("SELECT co.pcid, co.oid, co.cost, co.price, co.weight, co.ordering, ao.name, ao.aid FROM {uc_class_attribute_options} AS co LEFT JOIN {uc_attribute_options} AS ao ON co.oid = ao.oid AND co.pcid = '%s' WHERE ao.aid = %d ORDER BY co.ordering, ao.name", $nid, $attr_id); break; default: $attribute = db_fetch_object(db_query("SELECT * FROM {uc_attributes} WHERE aid = %d", $attr_id)); $result = db_query("SELECT * FROM {uc_attribute_options} WHERE aid = %d ORDER BY ordering, name, label", $attr_id); break; } if (isset($attribute->default_ordering) && is_null($attribute->ordering)) { $attribute->ordering = $attribute->default_ordering; } if (isset($attribute->default_required) && is_null($attribute->required)) { $attribute->required = $attribute->default_required; } if (isset($attribute->default_display) && is_null($attribute->display)) { $attribute->display = $attribute->default_display; } if (isset($attribute->default_label) && is_null($attribute->label)) { $attribute->label = $attribute->default_label; } if (empty($attribute->label)) { $attribute->label = $attribute->name; } } else { $attribute = db_fetch_object(db_query("SELECT * FROM {uc_attributes} WHERE aid = %d", $attr_id)); $result = db_query("SELECT * FROM {uc_attribute_options} WHERE aid = %d ORDER BY ordering, name", $attr_id); } if ($attribute) { $attribute->options = array(); while ($option = db_fetch_object($result)) { $attribute->options[$option->oid] = $option; } } return $attribute; } // Loads the option identified by $oid. function uc_attribute_option_load($oid) { return db_fetch_object(db_query("SELECT * FROM {uc_attribute_options} WHERE oid = %d", $oid)); } // Loads all attributes associated with a product node. function uc_product_get_attributes($nid) { $attributes = array(); $result = db_query("SELECT upa.aid FROM {uc_product_attributes} AS upa LEFT JOIN {uc_attributes} AS ua ON upa.aid = ua.aid WHERE upa.nid = %d ORDER BY upa.ordering, ua.name", $nid); while ($attribute = db_fetch_object($result)) { $attributes[$attribute->aid] = uc_attribute_load($attribute->aid, $nid, 'product'); } return $attributes; } // Loads all attributes associated with a product class. function uc_class_get_attributes($pcid) { $attributes = array(); $result = db_query("SELECT uca.aid FROM {uc_class_attributes} AS uca LEFT JOIN {uc_attributes} AS ua ON uca.aid = ua.aid WHERE uca.pcid = '%s' ORDER BY uca.ordering, ua.name", $pcid); while ($attribute = db_fetch_object($result)) { $attributes[$attribute->aid] = uc_attribute_load($attribute->aid, $pcid, 'class'); } return $attributes; } /** * Get the options chosen for a product that is in the cart. * * @param $item * An element of the array returned by uc_cart_get_contents. * @return * Array of options chosen by a customer, indexed by attribute ids. Each * element stores the attribute name and the option object chosen. */ function _uc_cart_product_get_options($item) { $options = array(); $data = $item->data; $node = node_load($item->nid); if (!empty($data['attributes']) && is_array($data['attributes'])) { foreach ($data['attributes'] as $aid => $oid) { $attribute = $node->attributes[$aid]; // Only discrete options can affect the price of an item. if ($attribute->display && count($attribute->options)) { $options[$aid] = (array)$attribute->options[$oid]; $options[$aid]['attribute'] = _uc_attribute_get_name($attribute); } else { $options[$aid] = array( 'attribute' => _uc_attribute_get_name($attribute), 'oid' => 0, 'name' => $oid, 'cost' => 0, 'price' => 0, 'weight' => 0, ); } } } else { $options = array(); } return $options; } /** * Helper function for uc_attribute_form_alter() */ function _uc_attribute_alter_form($product) { // If the product doesn't have attributes, return the form as it is. if (!isset($product->attributes)) { $product->attributes = uc_product_get_attributes($product->nid); } if (!is_array($product->attributes) || empty($product->attributes)) { return NULL; } $nid = $product->nid; $qty = $product->default_qty; if (!$qty) { $qty = 1; } // Load all product attributes for the given nid. $priced_attributes = uc_attribute_priced_attributes($nid); $attributes = $product->attributes; $form_attributes = array(); $context = array( 'revision' => 'formatted', 'location' => 'product-attribute-form-element', 'subject' => array(), ); // Loop through each product attribute and generate its form element. foreach ($attributes as $attribute) { $context['subject']['attribute'] = $attribute; // Build the attribute's options array. $options = array(); foreach ($attribute->options as $option) { $context['subject']['option'] = $option; switch (variable_get('uc_attribute_option_price_format', 'adjustment')) { case 'total': $price_info = array( 'price' => $product->sell_price + $option->price, 'qty' => $qty, ); $display_price = in_array($attribute->aid, $priced_attributes) ? ', '. uc_price($price_info, $context) : ''; if (count($priced_attributes) == 1) { break; } case 'adjustment': $price_info = array( 'price' => $option->price, 'qty' => $qty, ); $display_price = ($option->price != 0 ? ', '. ($option->price > 0 ? '+' : '') . uc_price($price_info, $context) : ''); break; case 'none': default: $display_price = ''; break; } $options[$option->oid] = $option->name; if ($attribute->display == 2) { // Select options are check_plain()ed, but radio button labels are not. $options[$option->oid] = check_plain($options[$option->oid]); } $options[$option->oid] .= $display_price; } if (count($attribute->options) && $attribute->display > 0) { if ($attribute->required) { if ($attribute->display == 1) { $options = array('' => t('Please select')) + $options; } unset($attribute->default_option); } $form_attributes[$attribute->aid] = array( '#type' => $attribute->display == 1 ? 'select' : 'radios', '#description' => check_markup($attribute->description), '#default_value' => $attribute->default_option, '#options' => $options, '#required' => $attribute->required, ); } else { $form_attributes[$attribute->aid] = array( '#type' => 'textfield', '#description' => check_markup($attribute->description), '#default_value' => $attribute->required == FALSE ? $attribute->options[$attribute->default_option]->name : '', '#required' => $attribute->required, ); } $name = _uc_attribute_get_name($attribute, FALSE); if (!is_null($name)) { $form_attributes[$attribute->aid]['#title'] = check_plain($name); } $form_attributes['#theme'] = 'uc_attribute_add_to_cart'; } return $form_attributes; } // Returns an array of display types used as options when creating attributes. function _uc_attribute_display_types() { return array( 0 => t('Text field'), 1 => t('Select box'), 2 => t('Radio buttons'), ); } /** * Get the price affecting attributes for a product * * @param $nid * The nid of a product. * @return * Array of attribute ids that have price affecting options. */ function uc_attribute_priced_attributes($nid) { $attributes = db_query("SELECT DISTINCT (pa.aid) FROM {uc_product_attributes} AS pa INNER JOIN {uc_attribute_options} AS ao ON ao.aid = pa.aid INNER JOIN {uc_product_options} AS po ON (po.oid = ao.oid AND po.nid = pa.nid) WHERE pa.nid = %d AND po.price <> 0 AND pa.display <> 0", $nid); $aids = array(); while ($attribute = db_fetch_array($attributes)) { $aids[] = $attribute['aid']; } return $aids; } function theme_uc_attribute_add_to_cart($form) { $output = '