diff --git a/modules/cart/commerce_cart.module b/modules/cart/commerce_cart.module index 9e22e46..270a1ff 100644 --- a/modules/cart/commerce_cart.module +++ b/modules/cart/commerce_cart.module @@ -359,6 +359,16 @@ function commerce_cart_form_field_ui_field_edit_form_alter(&$form, &$form_state) ), ), ); + $form['instance']['commerce_cart_settings']['single_product_display'] = array( + '#type' => 'checkbox', + '#title' => t('Display the widget if there is only product being displayed.'), + '#default_value' => $commerce_cart_settings['single_product_display'], + '#states' => array( + 'visible' => array( + ':input[name="instance[commerce_cart_settings][attribute_field]"]' => array('checked' => TRUE), + ), + ), + ); $form['field']['cardinality']['#description'] .= '
' . t('Must be 1 for this field to function as an attribute selection field on Add to Cart forms.'); } @@ -1436,342 +1446,329 @@ function commerce_cart_add_to_cart_form($form, &$form_state, $line_item, $show_q ); } else { - // 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_state['default_product'] = reset($products); + // 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. + $qualifying_fields = array(); + $same_type = TRUE; + $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; + } - $form['product_id'] = array( - '#type' => 'hidden', - '#value' => key($products), - ); - } - else { - // 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. - $qualifying_fields = array(); - $same_type = TRUE; - $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(); + // 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; + } - foreach ($products as $product_id => $product) { - $product_wrapper = entity_metadata_wrapper('commerce_product', $product); + // If the form state contains a set of attribute data, use it to try + // and determine the default product. + $changed_attribute = NULL; - // Store the first product type. - if (empty($type)) { - $type = $product->type; - } + if (!empty($form_state['values']['attributes'])) { + $match = TRUE; - // 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; + // 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']['attributes'], array('product_select' => '')); + $unchanged_attributes = $form_state['values']['unchanged_attributes']; } - // If the form state contains a set of attribute data, use it to try - // and determine the default product. - $changed_attribute = NULL; + 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; + } - if (!empty($form_state['values']['attributes'])) { - $match = TRUE; + // 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']['attributes'][$key]); - // 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']['attributes'], array('product_select' => '')); - $unchanged_attributes = $form_state['values']['unchanged_attributes']; + // Don't accept this as a matching product. + continue; } - 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; - } - - // 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']['attributes'][$key]); + if ($product_wrapper->{$key}->raw() != $value) { + $match = FALSE; + } + } - // Don't accept this as a matching product. - continue; - } + // 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); - if ($product_wrapper->{$key}->raw() != $value) { + foreach ($attribute_names as $key => $value) { + if ($product_wrapper->{$key}->raw() != $matching_product_wrapper->{$key}->raw()) { $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; - } + 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']['product_id']) && - !empty($products[$form_state['values']['product_id']])) { - $default_product = $products[$form_state['values']['product_id']]; - } - elseif (empty($form_state['values']) && - !empty($line_item_wrapper->commerce_product) && - !empty($products[$line_item_wrapper->commerce_product->raw()])) { - // If this is the first time the form is built, attempt to use the - // product specified by the line item. - $default_product = $products[$line_item_wrapper->commerce_product->raw()]; - } - else { - reset($products); - $default_product = $products[key($products)]; - } + // 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']['product_id']) && + !empty($products[$form_state['values']['product_id']])) { + $default_product = $products[$form_state['values']['product_id']]; + } + elseif (empty($form_state['values']) && + !empty($line_item_wrapper->commerce_product) && + !empty($products[$line_item_wrapper->commerce_product->raw()])) { + // If this is the first time the form is built, attempt to use the + // product specified by the line item. + $default_product = $products[$line_item_wrapper->commerce_product->raw()]; } else { - // If the product selector has a value, use that. - if (!empty($form_state['values']['attributes']['product_select']) && - !empty($products[$form_state['values']['attributes']['product_select']]) && - in_array($products[$form_state['values']['attributes']['product_select']], $matching_products)) { - $default_product = $products[$form_state['values']['attributes']['product_select']]; - } - else { - reset($matching_products); - $default_product = $matching_products[key($matching_products)]; - } + reset($products); + $default_product = $products[key($products)]; } + } + else { + // If the product selector has a value, use that. + if (!empty($form_state['values']['attributes']['product_select']) && + !empty($products[$form_state['values']['attributes']['product_select']]) && + in_array($products[$form_state['values']['attributes']['product_select']], $matching_products)) { + $default_product = $products[$form_state['values']['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['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 the instance is of a field type that is eligible to function as - // a product attribute field and if its attribute field settings - // specify that this functionality is enabled... - if (commerce_cart_field_attribute_eligible($field) && $commerce_cart_settings['attribute_field']) { - // Get the options properties from the options module and store the - // options for the instance in select list format in the array of - // qualifying fields. - $properties = _options_properties('select', 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'); - } + // Wrap the default product for later use. + $default_product_wrapper = entity_metadata_wrapper('commerce_product', $default_product); + + $form_state['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 the instance is of a field type that is eligible to function as + // a product attribute field and if its attribute field settings + // specify that this functionality is enabled... + if (commerce_cart_field_attribute_eligible($field) && $commerce_cart_settings['attribute_field']) { + // Get the options properties from the options module and store the + // options for the instance in select list format in the array of + // qualifying fields. + $properties = _options_properties('select', 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); } - // Otherwise just use the base language values. - if (empty($allowed_values)) { - $allowed_values = _options_get_options($field, $instance, $properties); + // Translate the field title if set. + if (!empty($instance['label'])) { + $instance['label'] = i18n_field_translate_property($instance, 'label'); } + } - // 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'], - ); - } + // Otherwise just use the base language values. + if (empty($allowed_values)) { + $allowed_values = _options_get_options($field, $instance, $properties); + } + + // 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'], + ); } } } + } - // Otherwise for products of varying types, display a simple select list - // by product title. - if (!empty($qualifying_fields)) { - $used_options = array(); + // Otherwise for products of varying types, display a simple select list + // by product title. + if (!empty($qualifying_fields)) { + $used_options = array(); - // Sort the fields by weight. - uasort($qualifying_fields, 'drupal_sort_weight'); + // Sort the fields by weight. + uasort($qualifying_fields, 'drupal_sort_weight'); - foreach ($qualifying_fields as $field_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 == $field_name) { - continue; - } + foreach ($qualifying_fields as $field_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); - if ($product_wrapper->{$used_field_name}->raw() != $form['attributes'][$used_field_name]['#default_value']) { - continue 2; - } + // 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 == $field_name) { + continue; } - // With our hard dependency on widgets provided by the Options - // module, we can make assumptions about where the data is stored. - $used_options[$field_name][] = $product_wrapper->{$field_name}->raw(); + if ($product_wrapper->{$used_field_name}->raw() != $form['attributes'][$used_field_name]['#default_value']) { + continue 2; + } } - // If for some reason no options for this field are used, remove it - // from the qualifying fields array. - if (empty($used_options[$field_name])) { - unset($qualifying_fields[$field_name]); - } - else { - $form['attributes'][$field_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[$field_name])), - '#default_value' => $default_product_wrapper->{$field_name}->raw(), - '#weight' => $data['instance']['widget']['weight'], - '#ajax' => array( - 'callback' => 'commerce_cart_add_to_cart_form_attributes_refresh', - ), - ); + // With our hard dependency on widgets provided by the Options + // module, we can make assumptions about where the data is stored. + $used_options[$field_name][] = $product_wrapper->{$field_name}->raw(); + } - // 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[$field_name])) { - $form['attributes'][$field_name]['#empty_value'] = ''; - } + // If for some reason no options for this field are used, remove it + // from the qualifying fields array. + if (empty($used_options[$field_name])) { + unset($qualifying_fields[$field_name]); + } + else { + $form['attributes'][$field_name] = array( + '#type' => $data['commerce_cart_settings']['single_product_display'] ? $data['commerce_cart_settings']['attribute_widget'] : 'hidden', + '#title' => check_plain($data['instance']['label']), + '#options' => array_intersect_key($data['options'], drupal_map_assoc($used_options[$field_name])), + '#default_value' => $default_product_wrapper->{$field_name}->raw(), + '#weight' => $data['instance']['widget']['weight'], + '#ajax' => array( + 'callback' => 'commerce_cart_add_to_cart_form_attributes_refresh', + ), + ); - $form['unchanged_attributes'][$field_name] = array( - '#type' => 'value', - '#value' => $default_product_wrapper->{$field_name}->raw(), - ); + // 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[$field_name])) { + $form['attributes'][$field_name]['#empty_value'] = ''; } - } - if (!empty($form['attributes'])) { - $form['attributes'] += array( - '#tree' => 'TRUE', - '#prefix' => '
', - '#suffix' => '
', - '#weight' => 0, - ); - $form['unchanged_attributes'] += array( - '#tree' => 'TRUE', + $form['unchanged_attributes'][$field_name] = array( + '#type' => 'value', + '#value' => $default_product_wrapper->{$field_name}->raw(), ); + } + } - // 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['attributes']) as $field_name) { - if ($product_wrapper->{$field_name}->raw() != $form['attributes'][$field_name]['#default_value']) { - $match = FALSE; - } - } + if (!empty($form['attributes'])) { + $form['attributes'] += array( + '#tree' => 'TRUE', + '#prefix' => '
', + '#suffix' => '
', + '#weight' => 0, + ); + $form['unchanged_attributes'] += array( + '#tree' => 'TRUE', + ); - if ($match) { - $matching_products[$product_id] = $product; + // 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['attributes']) as $field_name) { + if ($product_wrapper->{$field_name}->raw() != $form['attributes'][$field_name]['#default_value']) { + $match = FALSE; } } - } - - // 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); + if ($match) { + $matching_products[$product_id] = $product; } - - $form['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_cart_add_to_cart_form_attributes_refresh', - ), - ); } - - $form['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)) { - $options = array(); + // 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 ($products as $product_id => $product) { - $options[$product_id] = check_plain($product->title); + foreach ($matching_products as $product_id => $product) { + $options[$product_id] = check_plain($product->title); + } + + $form['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_cart_add_to_cart_form_attributes_refresh', + ), + ); } $form['product_id'] = array( - '#type' => 'select', - '#options' => $options, - '#default_value' => $default_product->product_id, - '#weight' => 0, - '#ajax' => array( - 'callback' => 'commerce_cart_add_to_cart_form_attributes_refresh', - ), + '#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)) { + $options = array(); + + foreach ($products as $product_id => $product) { + $options[$product_id] = check_plain($product->title); + } + + $form['product_id'] = array( + '#type' => 'select', + '#options' => $options, + '#default_value' => $default_product->product_id, + '#weight' => 0, + '#ajax' => array( + 'callback' => 'commerce_cart_add_to_cart_form_attributes_refresh', + ), + ); + } + // Render the quantity field as either a textfield if shown or a hidden // field if not. if ($show_quantity) {