function commerce_product_bundle_add_to_cart_form in Commerce Product Bundle 7.2
Same name and namespace in other branches
- 7 commerce_product_bundle.module \commerce_product_bundle_add_to_cart_form()
Builds the add to cart form, for product bundles.
Therefore it takes the add-to-cart-forms of each of the subproducts and put them together in one form.
@ToDo This are tons of duplicated code from 'commerce_cart_add_to_cart_form'. We should finde a way to reduce code duplication.
Parameters
array &$form: The standard commerce add to cart form for the bundle product.
array &$form_state: The form_state array for the bundle product.
int $parent_product_id: The ID of the bundle product.
array $product_ids: The ID's of the subproducts, aka the products which are part of the bundle.
array $field_instance: Document the field instance.
array $settings: Settings for this instance of the field.
array $context: Document Context array.
Return value
array The add to cart form with the bundle product.
1 call to commerce_product_bundle_add_to_cart_form()
- commerce_product_bundle_form_alter in ./
commerce_product_bundle.module - Implements of hook_bundle_form_alter().
File
- ./
commerce_product_bundle.module, line 207 - Allows the bundling of products in Drupal Commerce.
Code
function commerce_product_bundle_add_to_cart_form(&$form, &$form_state, $parent_product_id, $product_ids, $field_instance, $settings = array(), $context = array()) {
global $user;
// Store the context in the form state for use during AJAX refreshes.
$form_state['context'] = $context;
// Get display settings.
if (isset($field_instance['display']['node_full'])) {
$display = $field_instance['display']['node_full'];
}
elseif (isset($field_instance['display']['default'])) {
$display = $field_instance['display']['default'];
}
else {
$display = array();
}
// Skip if we are not using the commerce_bundle_product_add_to_cart_form.
if (!isset($display['type']) || $display['type'] != 'commerce_bundle_product_add_to_cart_form') {
return array();
}
// Load all the products intended for sale on this form.
$products = $product_ids ? commerce_product_load_multiple($product_ids, array(
'status' => 1,
)) : array();
// If no products were returned, go home.
// @ToDo Commerce has a more user friendly approach, which uses a disabled submit button.
// Steal that and implement here.
if (count($products) == 0) {
return array();
}
if (!isset($display['settings'])) {
$display['settings'] = array();
}
$settings = $display['settings'] + commerce_product_bundle_field_formatter_default_settings();
$weight = $display['weight'];
$ref_field_name = $field_instance['field_name'];
$id = $parent_product_id . '__' . $ref_field_name;
// Add a generic class ID.
$form['#attributes']['class'][] = drupal_html_class('commerce-product-bundle-add-to-cart');
// Load data from line item id.
if (isset($form_state['line_item']) && !isset($form_state['values']['bundle'][$id]['product_id'])) {
$sub_line_items = commerce_product_bundle_get_sub_line_items($form_state['line_item']);
// We assume that we have never the same product in one bundle.
// This means that we let the customer never choose the same product
// in the same bundle.
foreach ($sub_line_items as $sub_line_item) {
$sub_line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $sub_line_item);
$sub_product_id = $sub_line_item_wrapper->commerce_product->product_id
->value();
if (isset($products[$sub_product_id])) {
$form_state['values']['bundle'][$id]['product_id'] = $sub_product_id;
$settings['default_quantity'] = (double) $sub_line_item_wrapper->quantity
->value();
break;
}
}
}
if (!isset($form['bundle'])) {
$form['bundle'] = array();
}
if (!isset($form['bundle'][$id])) {
$form['bundle'][$id] = array();
}
$form['bundle'] += array(
'#tree' => TRUE,
);
$form['bundle'][$id] += array(
'#tree' => TRUE,
'#prefix' => '<div class="bundle-widgets">',
'#suffix' => '</div>',
'#weight' => $weight,
);
// If the form is for a single product, store the product_id in a hidden
// form field for use by the submit handler.
if (count($products) == 1) {
$form['bundle'][$id]['product_id'] = array(
'#type' => 'hidden',
'#value' => key($products),
);
}
if ($settings['show_fieldset']) {
$form['bundle'][$id] += array(
'#type' => 'fieldset',
'#title' => $field_instance['label'],
);
}
$form_state['bundle_products'][$id] = $products;
// However, if more than one products are represented on it, attempt to
// use smart select boxes for the product selection. If the products are
// all of the same type and there are qualifying fields on that product
// type, display their options for customer selection.
$same_type = TRUE;
$qualifying_fields = array();
$type = '';
// Find the default product so we know how to set default options on the
// various Add to Cart form widgets and an array of any matching product
// based on attribute selections so we can add a selection widget.
$matching_products = array();
$default_product = NULL;
$attribute_names = array();
$unchanged_attributes = array();
foreach ($products as $product_id => $product) {
$product_wrapper = entity_metadata_wrapper('commerce_product', $product);
// Store the first product type.
if (empty($type)) {
$type = $product->type;
}
// If the current product type is different from the first, we are not
// dealing with a set of same typed products.
if ($product->type != $type) {
$same_type = FALSE;
}
// If the form state contains a set of attribute data, use it to try
// and determine the default product.
$changed_attribute = NULL;
if (!empty($form_state['values']['bundle'][$id]['attributes'])) {
$match = TRUE;
// Set an array of checked attributes for later comparison against the
// default matching product.
if (empty($attribute_names)) {
$attribute_names = (array) array_diff_key($form_state['values']['bundle'][$id]['attributes'], array(
'product_select' => '',
));
$unchanged_attributes = $form_state['values']['bundle'][$id]['unchanged_attributes'];
}
foreach ($attribute_names as $key => $value) {
// If this is the attribute widget that was changed...
if ($value != $unchanged_attributes[$key]) {
// Store the field name.
$changed_attribute = $key;
// Clear the input for the "Select a product" widget now if it
// exists on the form since we know an attribute was changed.
unset($form_state['input']['bundle'][$id]['attributes']['product_select']);
}
// If a field name has been stored and we've moved past it to
// compare the next attribute field...
if (!empty($changed_attribute) && $changed_attribute != $key) {
// Wipe subsequent values from the form state so the attribute
// widgets can use the default values from the new default product.
unset($form_state['input']['bundle'][$id]['attributes'][$key]);
// Don't accept this as a matching product.
continue;
}
if ($product_wrapper->{$key}
->raw() != $value) {
$match = FALSE;
}
}
// If the changed field name has already been stored, only accept the
// first matching product by ignoring the rest that would match. An
// exception is granted for additional matching products that share
// the exact same attribute values as the first.
if ($match && !empty($changed_attribute) && !empty($matching_products)) {
reset($matching_products);
$matching_product = $matching_products[key($matching_products)];
$matching_product_wrapper = entity_metadata_wrapper('commerce_product', $matching_product);
foreach ($attribute_names as $key => $value) {
if ($product_wrapper->{$key}
->raw() != $matching_product_wrapper->{$key}
->raw()) {
$match = FALSE;
}
}
}
if ($match) {
$matching_products[$product_id] = $product;
}
}
}
// Set the default product now if it isn't already set.
if (empty($matching_products)) {
// If a product ID value was passed in, use that product if it exists.
if (!empty($form_state['values']['bundle'][$id]['product_id']) && !empty($products[$form_state['values']['bundle'][$id]['product_id']])) {
$default_product = $products[$form_state['values']['bundle'][$id]['product_id']];
}
else {
reset($products);
$default_product = $products[key($products)];
}
}
else {
// If the product selector has a value, use that.
if (!empty($form_state['values']['bundle'][$id]['attributes']['product_select']) && !empty($products[$form_state['values']['bundle'][$id]['attributes']['product_select']]) && in_array($products[$form_state['values']['bundle'][$id]['attributes']['product_select']], $matching_products)) {
$default_product = $products[$form_state['values']['bundle'][$id]['attributes']['product_select']];
}
else {
reset($matching_products);
$default_product = $matching_products[key($matching_products)];
}
}
// Wrap the default product for later use.
$default_product_wrapper = entity_metadata_wrapper('commerce_product', $default_product);
$form_state['bundle'][$id]['default_product'] = $default_product;
// If all the products are of the same type...
if ($same_type) {
// Loop through all the field instances on that product type.
foreach (field_info_instances('commerce_product', $type) as $name => $instance) {
// A field qualifies if it is single value, required and uses a widget
// with a definite set of options. For the sake of simplicity, this is
// currently restricted to fields defined by the options module.
$field = field_info_field($instance['field_name']);
// Get the array of Cart settings pertaining to this instance.
$commerce_cart_settings = commerce_cart_field_instance_attribute_settings($instance);
if (commerce_cart_field_attribute_eligible($field) && $commerce_cart_settings['attribute_field']) {
// Get the options properties from the options module for the
// attribute widget type selected for the field, defaulting to the
// select list options properties.
switch ($commerce_cart_settings['attribute_widget']) {
case 'checkbox':
$widget_type = 'onoff';
break;
case 'radios':
$widget_type = 'buttons';
break;
default:
$widget_type = 'select';
}
$properties = _options_properties($widget_type, FALSE, TRUE, TRUE);
// Try to fetch localized names.
$allowed_values = NULL;
// Prepare translated options if using the i18n_field module.
if (module_exists('i18n_field')) {
if ($translate = i18n_field_type_info($field['type'], 'translate_options')) {
$allowed_values = $translate($field);
_options_prepare_options($allowed_values, $properties);
}
// Translate the field title if set.
if (!empty($instance['label'])) {
$instance['label'] = i18n_field_translate_property($instance, 'label');
}
}
// Otherwise just use the base language values.
if (empty($allowed_values)) {
$allowed_values = _options_get_options($field, $instance, $properties, 'commerce_product', $default_product);
}
// Only consider this field a qualifying attribute field if we could
// derive a set of options for it.
if (!empty($allowed_values)) {
$qualifying_fields[$name] = array(
'field' => $field,
'instance' => $instance,
'commerce_cart_settings' => $commerce_cart_settings,
'options' => $allowed_values,
'weight' => $instance['widget']['weight'],
'required' => $instance['required'],
);
}
}
}
}
// Generate the select form items if we have only one product type,
// which implies that all products has the same fields / attributes.
if (!empty($qualifying_fields)) {
$used_options = array();
$field_has_options = array();
// Sort the fields by weight.
uasort($qualifying_fields, 'drupal_sort_weight');
foreach ($qualifying_fields as $name => $data) {
// Build an options array of widget options used by referenced products.
foreach ($products as $product_id => $product) {
$product_wrapper = entity_metadata_wrapper('commerce_product', $product);
// Only add options to the present array that appear on products that
// match the default value of the previously added attribute widgets.
foreach ($used_options as $used_field_name => $unused) {
// Don't apply this check for the current field being evaluated.
if ($used_field_name == $name) {
continue;
}
if (isset($form['bundle'][$id]['attributes'][$used_field_name]['#default_value'])) {
if ($product_wrapper->{$used_field_name}
->raw() != $form['bundle'][$id]['attributes'][$used_field_name]['#default_value']) {
continue 2;
}
}
}
// With our hard dependency on widgets provided by the Options
// module, we can make assumptions about where the data is stored.
if ($product_wrapper->{$name}
->raw() != NULL) {
$field_has_options[$name] = TRUE;
}
$used_options[$name][] = $product_wrapper->{$name}
->raw();
}
// If for some reason no options for this field are used, remove it
// from the qualifying fields array.
if (empty($field_has_options[$name]) || empty($used_options[$name])) {
unset($qualifying_fields[$name]);
}
else {
$form['bundle'][$id]['attributes'][$name] = array(
'#type' => $data['commerce_cart_settings']['attribute_widget'],
'#title' => check_plain($data['instance']['label']),
'#options' => array_intersect_key($data['options'], drupal_map_assoc($used_options[$name])),
'#default_value' => $default_product_wrapper->{$name}
->raw(),
'#weight' => $data['instance']['widget']['weight'],
'#ajax' => array(
'callback' => 'commerce_product_attributes_add_to_cart_form_attributes_refresh',
),
);
// Add the empty value if the field is not required and products on
// the form include the empty value.
if (!$data['required'] && in_array('', $used_options[$name])) {
$form['bundle'][$id]['attributes'][$name]['#empty_value'] = '';
}
$form['bundle'][$id]['unchanged_attributes'][$name] = array(
'#type' => 'value',
'#value' => $default_product_wrapper->{$name}
->raw(),
);
}
}
if (!empty($form['bundle'][$id]['attributes'])) {
$form['bundle'][$id]['attributes'] += array(
'#tree' => 'TRUE',
'#prefix' => '<div class="attribute-widgets">',
'#suffix' => '</div>',
'#weight' => 0,
);
$form['bundle'][$id]['unchanged_attributes'] += array(
'#tree' => 'TRUE',
);
// If the matching products array is empty, it means this is the first
// time the form is being built. We should populate it now with
// products that match the default attribute options.
if (empty($matching_products)) {
foreach ($products as $product_id => $product) {
$product_wrapper = entity_metadata_wrapper('commerce_product', $product);
$match = TRUE;
foreach (element_children($form['bundle'][$id]['attributes']) as $name) {
if ($product_wrapper->{$name}
->raw() != $form['bundle'][$id]['attributes'][$name]['#default_value']) {
$match = FALSE;
}
}
if ($match) {
$matching_products[$product_id] = $product;
}
}
}
// If there were more than one matching products for the current
// attribute selection, add a product selection widget.
if (count($matching_products) > 1) {
$options = array();
foreach ($matching_products as $product_id => $product) {
$options[$product_id] = check_plain($product->title);
}
$form['bundle'][$id]['attributes']['product_select'] = array(
'#type' => 'select',
'#title' => t('Select a product'),
'#options' => $options,
'#default_value' => $default_product->product_id,
'#weight' => 40,
'#ajax' => array(
'callback' => 'commerce_product_attributes_add_to_cart_form_attributes_refresh',
),
);
}
$form['bundle'][$id]['product_id'] = array(
'#type' => 'hidden',
'#value' => $default_product->product_id,
);
}
}
// If the products referenced were of different types or did not posess
// any qualifying attribute fields, add a product selection widget.
if (!$same_type || empty($qualifying_fields)) {
// For a single product form, just add the hidden product_id field.
if (count($products) == 1) {
$form['bundle'][$id]['product_id'] = array(
'#type' => 'hidden',
'#value' => $default_product->product_id,
);
}
else {
$options = array();
foreach ($products as $product_id => $product) {
$options[$product_id] = check_plain($product->title);
}
$form['bundle'][$id]['product_id'] = array(
'#type' => 'select',
'#options' => $options,
'#default_value' => $default_product->product_id,
'#weight' => 0,
'#ajax' => array(
'callback' => 'commerce_product_attributes_add_to_cart_form_attributes_refresh',
),
);
}
}
// Render the quantity field as either a textfield if shown or a hidden
// field if not.
if ($settings['show_quantity']) {
$form['bundle'][$id]['quantity'] = array(
'#type' => 'textfield',
'#title' => t('Quantity'),
'#default_value' => $settings['default_quantity'],
'#datatype' => 'integer',
'#size' => 5,
'#weight' => 5,
);
}
else {
$form['bundle'][$id]['quantity'] = array(
'#type' => 'hidden',
'#value' => $settings['default_quantity'],
'#datatype' => 'integer',
'#weight' => 5,
);
}
// Do not allow bundle products without a price to be purchased.
$values = commerce_product_calculate_sell_price($form_state['default_product']);
if (is_null($values) || is_null($values['amount']) || is_null($values['currency_code'])) {
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Product not available'),
'#weight' => 50,
// Do not set #disabled in order not to prevent submission.
'#attributes' => array(
'disabled' => 'disabled',
),
'#validate' => array(
'commerce_cart_add_to_cart_form_disabled_validate',
),
);
}
else {
// Remove the default submit handler, we need our own handler.
foreach ($form['#submit'] as $handler_id => $handler) {
if ($handler == 'commerce_cart_add_to_cart_form_submit') {
unset($form['#submit'][$handler_id]);
}
elseif ($handler == 'commerce_product_attributes_add_to_cart_form_submit') {
unset($form['#submit'][$handler_id]);
}
elseif ($handler == 'commerce_product_bundle_add_to_cart_form_submit') {
unset($form['#submit'][$handler_id]);
}
}
}
// Add the handlers manually since we're using hook_forms() to associate this
// form with form IDs based on the $product_ids.
$form['#validate'][] = 'commerce_product_bundle_add_to_cart_form_validate';
$form['#submit'][] = 'commerce_product_bundle_add_to_cart_form_submit';
return $form;
}