You are here

uc_dropdown_attributes.module in Dropdown Attributes 7

Same filename and directory in other branches
  1. 8 uc_dropdown_attributes.module
  2. 6 uc_dropdown_attributes.module

Show/hide attributes based on the values of other attributes.

Some attributes may not be applicable depending upon the value of another attribute. It may be desireable to hide the attribute unless an appropriate value is selected for the other attribute to avoid confusing users. This module has an administrative interface for specifying the dependencies and Javascript code for hiding and showing the attributes.

File

uc_dropdown_attributes.module
View source
<?php

/**
 * @file
 * Show/hide attributes based on the values of other attributes.
 *
 * Some attributes may not be applicable depending upon the value of another
 * attribute.  It may be desireable to hide the attribute unless an appropriate
 * value is selected for the other attribute to avoid confusing users.  This
 * module has an administrative interface for specifying the dependencies
 * and Javascript code for hiding and showing the attributes.
 */

/**
 * Implements hook_menu().
 */
function uc_dropdown_attributes_menu() {
  $items = array();
  $items['node/%node/edit/dependencies'] = array(
    'title' => 'Dependencies',
    'description' => 'Product attribute dependency administration.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'uc_dropdown_attributes_product',
      1,
    ),
    'access callback' => 'uc_attribute_product_access',
    'access arguments' => array(
      1,
    ),
    'theme callback' => 'uc_dropdown_attributes_admin_theme',
    'type' => MENU_LOCAL_TASK,
    'weight' => 1,
    'file' => 'dependent_dropdown.inc',
  );
  $items['node/%/dependencies/%/dependency/%'] = array(
    'type' => MENU_CALLBACK,
    'page callback' => 'uc_dropdown_attributes_dependency',
    'page arguments' => array(
      1,
      3,
      5,
    ),
    'access callback' => TRUE,
  );
  $items['admin/store/products/classes/%/dependencies'] = array(
    'title' => 'Dependencies',
    'description' => 'Product class attribute dependency administration.',
    'type' => MENU_LOCAL_TASK,
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'uc_dropdown_attributes_class',
      4,
    ),
    'access arguments' => array(
      'administer product classes',
    ),
    'file' => 'dependent_dropdown.inc',
  );
  return $items;
}

/**
 * Implements hook_form_alter().
 */
function uc_dropdown_attributes_form_alter(&$form, &$form_state, $form_id) {
  if (preg_match('/^uc_product_kit_add_to_cart_form.*/', $form_id)) {
    foreach ($form['products'] as $key => $value) {
      if (is_numeric($key)) {
        $type = uc_dropdown_attributes_dependency_type($key);
        if (!is_null($type)) {
          if (isset($form_state['values'])) {
            $values = $form_state['values'];
          }
          else {
            $values = array();
          }
          uc_dropdown_attributes_product_alter($key, $form['products'][$key], $values, $type, $form_state['rebuild']);

          // Make sure these have not been added more than once.
          if (!isset($form['#after_build']) || !in_array('_uc_dropdown_attributes_kit_build', $form['#after_build'])) {
            $form['#after_build'][] = '_uc_dropdown_attributes_kit_build';
          }
        }
      }
    }
  }
  if (preg_match('/^uc_product_add_to_cart_form.*/', $form_id)) {
    $nid = $form['nid']['#value'];
    $type = uc_dropdown_attributes_dependency_type($nid);
    if (!is_null($type)) {
      if (isset($form_state['values'])) {
        $values = $form_state['values'];
      }
      else {
        $values = array();
      }
      uc_dropdown_attributes_product_alter($nid, $form, $values, $type, $form_state['rebuild']);
      $form['node_id'] = array(
        '#type' => 'hidden',
        '#value' => $nid,
      );
      switch ($type) {
        case 'node':
          $form['#after_build'][] = '_uc_dropdown_attributes_product_build';
          break;
        case 'class':
          $form['#after_build'][] = '_uc_dropdown_attributes_class_build';
          break;
      }
    }
  }
}

/**
 * Alter products in preparation for drop down attributes.
 *
 * Adds the 'Please select' and removes the default value.  Ubercart does this
 * for required attributes but since these attributes can no longer be required
 * if the attributes are dependent then this reproduces the same thing.
 *
 * @param int $nid
 *   Node ID.
 * @param array $form
 *   Product form.
 * @param array $form_values
 *   Values from $form_state.
 * @param string $type
 *   'node' for dependencies defined on the node level; 'class' for dependencies
 *   defined on the product class.
 * @param bool $rebuild
 *   Whether the form is being rebuilt.
 */
function uc_dropdown_attributes_product_alter($nid, &$form, $form_values, $type, $rebuild) {
  switch ($type) {
    case 'node':
      $sql = 'SELECT aid, parent_aid, parent_values, required
        FROM {uc_dropdown_attributes}
        WHERE nid=:nid';
      $attributes = db_query($sql, array(
        ':nid' => $nid,
      ));
      break;
    case 'class':
      $sql = 'SELECT aid, parent_aid, parent_values, required
        FROM {uc_dropdown_classes}
        WHERE pcid=:pcid';
      $pcid = uc_dropdown_attributes_get_type($nid);
      $attributes = db_query($sql, array(
        ':pcid' => $pcid,
      ));
      break;
  }
  $parent_aids = array();
  if (!$rebuild) {
    $data = $form['node']['#value']->data;
  }
  $load = FALSE;
  foreach ($attributes as $attribute) {
    $parent_aids[$attribute->parent_aid] = $attribute->parent_aid;
    if (isset($form['attributes'][$attribute->aid]['#options']) && count($form['attributes'][$attribute->aid]['#options']) && $attribute->required) {
      switch ($form['attributes'][$attribute->aid]['#type']) {
        case 'select':
          $form['attributes'][$attribute->aid]['#options'] = array(
            '' => t('Please select'),
          ) + $form['attributes'][$attribute->aid]['#options'];
          $form['attributes'][$attribute->aid]['#default_value'] = '';
          if (!$rebuild && isset($data['attributes'][$attribute->aid])) {
            $data['attributes'][$attribute->aid] = '';
            $load = TRUE;
          }
          break;
        case 'radios':
          $form['attributes'][$attribute->aid]['#default_value'] = NULL;
          if (!$rebuild && isset($data['attributes'][$attribute->aid])) {
            $data['attributes'][$attribute->aid] = '';
            $load = TRUE;
          }

          // Validation does not work if required set later.
          if (isset($form_values['attributes'][$attribute->parent_aid])) {
            $parent_value = $form_values['attributes'][$attribute->parent_aid];
            $values = unserialize($attribute->parent_values);
            $parent_aid = $attribute->parent_aid;
            if (in_array($parent_value, $values)) {
              $form['attributes'][$attribute->aid]['#required'] = TRUE;
            }
          }
          break;
        case 'checkboxes':
          $form['attributes'][$attribute->aid]['#default_value'] = array();
          if (!$rebuild && isset($data['attributes'][$attribute->aid])) {
            $data['attributes'][$attribute->aid] = array();
            $load = TRUE;
          }
          break;
      }
    }
  }
  if ($load) {
    $form['node']['#value'] = uc_product_load_variant($nid, $data);
  }
  $update_node = variable_get('uc_product_update_node_view', 0);
  if (!$update_node) {

    // If Ubercart update is not enabled then ajax needs to be attached to
    // parent attributes.
    foreach ($parent_aids as $aid) {
      $form['attributes'][$aid]['#ajax'] = array(
        'callback' => 'uc_dropdown_attributes_ajax_callback',
        'wrapper' => $form['attributes']['#id'],
      );
    }
  }
}

/**
 * Ajax callback for attribute selection form elements.
 */
function uc_dropdown_attributes_ajax_callback($form, $form_state) {
  if (count($form_state['triggering_element']['#parents']) == 4) {

    // This is a product kit.
    $key = $form_state['triggering_element']['#parents'][1];
    return $form['products'][$key]['attributes'];
  }
  return $form['attributes'];
}

/**
 * Form build for products.
 *
 * Callback for $form['#after_build'] for products. Adds the CSS to hide
 * the dependent attributes.
 */
function _uc_dropdown_attributes_product_build($form, &$form_state) {
  $nid = $form['nid']['#value'];
  $sql = 'SELECT aid, parent_aid, parent_values, required
    FROM {uc_dropdown_attributes} WHERE nid=:nid';
  $attributes = db_query($sql, array(
    ':nid' => $nid,
  ));
  if (isset($form_state['triggering_element'])) {
    $parents = $form_state['triggering_element']['#parents'];
    $parent_aid = $parents[count($parents) - 1];
    $parent_value = $form_state['triggering_element']['#value'];
    uc_dropdown_attributes_remove_values($parent_aid, $parent_value, $nid, 'node', $form_state['values']);
  }
  foreach ($attributes as $attribute) {
    $parent_value = $form_state['values']['attributes'][$attribute->parent_aid];
    if ($form['attributes'][$attribute->parent_aid]['#type'] == 'checkboxes') {
      $parent_values = array_diff($parent_value, array(
        0,
      ));
      $values = unserialize($attribute->parent_values);
      if (count(array_intersect($parent_values, $values))) {

        // Show dependent attribute.
        if ($attribute->required) {
          $form['attributes'][$attribute->aid]['#required'] = TRUE;
        }
      }
      else {

        // Hide dependent attribute.
        $form['attributes'][$attribute->aid]['#post_render'][] = 'uc_dropdown_attributes_post_render';
        if ($attribute->required) {
          uc_dropdown_attributes_clear_input($form, $form_state, $attribute->aid);
        }
      }
    }
    else {
      if ($parent_value) {

        // A value has been entered in parent attribute.
        $values = unserialize($attribute->parent_values);
        if (array_key_exists($parent_value, $values)) {

          // Show dependent attribute.
          if ($attribute->required) {
            $form['attributes'][$attribute->aid]['#required'] = TRUE;
          }
        }
        else {

          // Hide dependent attribute.
          $form['attributes'][$attribute->aid]['#post_render'][] = 'uc_dropdown_attributes_post_render';
          if ($attribute->required) {
            uc_dropdown_attributes_clear_input($form, $form_state, $attribute->aid);
          }
        }
      }
      else {
        $form['attributes'][$attribute->aid]['#post_render'][] = 'uc_dropdown_attributes_post_render';
        if ($attribute->required) {
          uc_dropdown_attributes_clear_input($form, $form_state, $attribute->aid);
        }
      }
    }
  }
  return $form;
}

/**
 * Clear inputs for attribute.
 *
 * @param array $form
 *   Form array.
 * @param array $form_state
 *   Form state array.
 * @param int $aid
 *   Attribute ID.
 */
function uc_dropdown_attributes_clear_input(&$form, &$form_state, $aid) {
  switch ($form['attributes'][$aid]['#type']) {
    case 'checkboxes':
      if (isset($form_state['input']['attributes'])) {
        foreach ($form_state['input']['attributes'][$aid] as $oid => $value) {
          $form_state['input']['attributes'][$aid][$oid] = NULL;
        }
      }
      break;
    case 'radios':
      $form_state['input']['attributes'][$aid] = NULL;
      break;
    case 'select':
      if ($form['attributes'][$aid]['#value'] != '') {
        $form['attributes'][$aid]['#value'] = '';
      }
      break;
    case 'textfield':
      $form_state['input']['attributes'][$aid] = '';
      break;
    default:
      break;
  }
}

/**
 * Form build for classes.
 *
 * Callback for $form['#after_build'] for product classes.  Adds
 * the CSS to hide the dependent attributes.
 */
function _uc_dropdown_attributes_class_build($form, &$form_state) {
  $sql = 'SELECT aid, parent_aid, parent_values, required
    FROM {uc_dropdown_classes} WHERE pcid=:pcid';
  $pcid = uc_dropdown_attributes_get_type($form['nid']['#value']);
  $attributes = db_query($sql, array(
    ':pcid' => $pcid,
  ));
  if (isset($form_state['triggering_element'])) {
    $parents = $form_state['triggering_element']['#parents'];
    $parent_aid = $parents[count($parents) - 1];
    $parent_value = $form_state['triggering_element']['#value'];
    uc_dropdown_attributes_remove_values($parent_aid, $parent_value, $pcid, 'class', $form_state['values']);
  }
  foreach ($attributes as $attribute) {
    if (isset($form_state['values']['attributes'])) {
      $parent_value = $form_state['values']['attributes'][$attribute->parent_aid];
    }
    if ($parent_value) {

      // A value has been entered in parent attribute.
      $values = unserialize($attribute->parent_values);
      if ($form['attributes'][$attribute->parent_aid]['#type'] == 'checkboxes') {
        $parent_values = array_diff($parent_value, array(
          0,
        ));
        if (count(array_intersect($parent_values, $values))) {

          // Show dependent attribute.
          if ($attribute->required) {
            $form['attributes'][$attribute->aid]['#required'] = TRUE;
          }
        }
        else {

          // Hide dependent attribute.
          $form['attributes'][$attribute->aid]['#post_render'][] = 'uc_dropdown_attributes_post_render';
          if ($attribute->required && count($form['attributes'][$attribute->aid]['#value']) > 0) {
            $form['attributes'][$attribute->aid]['#value'] = array();
          }
        }
      }
      else {
        if (array_key_exists($parent_value, $values)) {

          // Show dependent attribute.
          if ($attribute->required) {
            $form['attributes'][$attribute->aid]['#required'] = TRUE;
          }
        }
        else {

          // Hide dependent attribute.
          $form['attributes'][$attribute->aid]['#post_render'][] = 'uc_dropdown_attributes_post_render';
          if ($attribute->required && $form['attributes'][$attribute->aid]['#value'] != '') {
            $form['attributes'][$attribute->aid]['#value'] = '';
          }
        }
      }
    }
    else {
      $form['attributes'][$attribute->aid]['#post_render'][] = 'uc_dropdown_attributes_post_render';
      if ($attribute->required && isset($form['attributes'][$attribute->aid]['#value']) && $form['attributes'][$attribute->aid]['#value'] != '') {
        $form['attributes'][$attribute->aid]['#value'] = '';
      }
    }
  }
  return $form;
}

/**
 * Form build for product kits.
 *
 * Callback for $form['#after_build'] for product kits. Renders the dependent
 * attributes and stores the html as a Javascript array.
 */
function _uc_dropdown_attributes_kit_build($form, &$form_state) {
  foreach ($form['products'] as $key => $value) {
    if (is_numeric($key)) {
      $type = uc_dropdown_attributes_dependency_type($key);
      switch ($type) {
        case 'node':
          $sql = 'SELECT aid, parent_aid, parent_values, required
            FROM {uc_dropdown_attributes} WHERE nid=:nid';
          $id = $key;
          $attributes = db_query($sql, array(
            ':nid' => $key,
          ));
          break;
        case 'class':
          $sql = 'SELECT aid, parent_aid, parent_values, required
            FROM {uc_dropdown_classes} WHERE pcid=:pcid';
          $pcid = uc_dropdown_attributes_get_type($key);
          $id = $pcid;
          $attributes = db_query($sql, array(
            ':pcid' => $pcid,
          ));
          break;
        default:
          $attributes = array();
      }
      if (isset($form_state['triggering_element'])) {
        $parents = $form_state['triggering_element']['#parents'];
        $parent_aid = $parents[count($parents) - 1];
        $parent_value = $form_state['triggering_element']['#value'];
        uc_dropdown_attributes_remove_values($parent_aid, $parent_value, $id, $type, $form_state['values']['products'][$key]);
      }
      foreach ($attributes as $attribute) {
        $aid = $attribute->aid;
        $parent_aid = $attribute->parent_aid;
        $parent_value = $form_state['values']['products'][$key]['attributes'][$parent_aid];
        if ($parent_value) {

          // A value has been entered in parent attribute.
          $values = unserialize($attribute->parent_values);
          if (array_key_exists($parent_value, $values)) {

            // Show dependent attribute.
            if ($attribute->required) {
              $form['products'][$key]['attributes'][$aid]['#required'] = TRUE;
            }
          }
          else {

            // Hide dependent attribute.
            $form['products'][$key]['attributes'][$aid]['#post_render'][] = 'uc_dropdown_attributes_post_render';
            if ($form['products'][$key]['attributes'][$aid]['#required'] && $form['products'][$key]['attributes'][$aid]['#value'] != '') {
              $form['products'][$key]['attributes'][$aid]['#value'] = '';
            }
          }
        }
        else {
          $form['products'][$key]['attributes'][$aid]['#post_render'][] = 'uc_dropdown_attributes_post_render';
          if ($form['products'][$key]['attributes'][$aid]['#required'] && $form['products'][$key]['attributes'][$aid]['#value'] != '') {
            $form['products'][$key]['attributes'][$aid]['#value'] = '';
          }
        }
      }
    }
  }
  return $form;
}

/**
 * Unset values of orphaned children.
 *
 * Called recursively to remove orphaned values from form_state.
 *
 * @param int $parent_aid
 *   Attribute ID of the triggering element.
 * @param int $parent_value
 *   Option ID of the value of the triggering element.
 * @param int $id
 *   Node ID or product class ID.
 * @param string $type
 *   'node' or 'class'.
 * @param array $form_values
 *   Part of the form_state array containing the attributes.
 */
function uc_dropdown_attributes_remove_values($parent_aid, $parent_value, $id, $type, &$form_values) {
  switch ($type) {
    case 'node':
      $sql = 'SELECT aid, required, parent_values FROM {uc_dropdown_attributes}
        WHERE nid=:nid AND parent_aid=:parent_aid';
      $children = db_query($sql, array(
        ':nid' => $id,
        ':parent_aid' => $parent_aid,
      ));
      break;
    case 'class':
      $sql = 'SELECT aid, required, parent_values FROM {uc_dropdown_classes}
        WHERE pcid=:pcid AND parent_aid=:parent_aid';
      $children = db_query($sql, array(
        ':pcid' => $id,
        ':parent_aid' => $parent_aid,
      ));
      break;
    default:
      $children = array();
      break;
  }
  foreach ($children as $child) {
    if ($child->required) {
      $value = $form_values['attributes'][$child->aid];
      $form_values['attributes'][$child->aid] = '';
      if ($value) {
        $values = unserialize($child->parent_values);
        if (!array_key_exists($parent_value, $values)) {
          uc_dropdown_attributes_remove_values($child->aid, $value, $id, $type, $form_values);
        }
      }
    }
  }
}

/**
 * Add the style to hide the attribute.
 *
 * @param string $html
 *   HTML for the element.
 *
 * @return string
 *   Modified element HTML.
 */
function uc_dropdown_attributes_post_render($html) {
  $pos = strpos($html, '>');
  $html = substr_replace($html, ' style="display:none;">', $pos, 1);
  return $html;
}

/**
 * Retrieves the attribute dependencies.
 *
 * Callback to supply attribute dependencies to Javascript.
 *
 * @param int $nid
 *   Node ID.
 * @param string $id
 *   String containing attribute ID.
 *
 * @return object
 *   JSON structure.
 */
function uc_dropdown_attributes_dependency($nid, $id, $parent_id) {
  $temp = explode('-', $id);
  if ($temp[1] == 'attributes') {
    $aid = $temp[2];
  }
  else {
    $aid = $temp[4];
  }
  $result = new stdClass();
  $query = 'SELECT parent_values, required
    FROM {uc_dropdown_attributes} WHERE nid=:nid && aid=:aid';
  $db_result = db_query($query, array(
    ':nid' => $nid,
    ':aid' => $aid,
  ));
  foreach ($db_result as $item) {
    $result->status = TRUE;
    $result->nid = $nid;
    $result->id = $id;
    $result->parent_id = $parent_id;
    $result->parent_values = unserialize($item->parent_values);
    $result->required = $item->required;
    drupal_json_output($result);
    return;
  }
  $pcid = uc_dropdown_attributes_get_type($nid);
  $query = 'SELECT parent_values, required
    FROM {uc_dropdown_classes} WHERE pcid=:pcid && aid=:aid';
  $db_result = db_query($query, array(
    ':pcid' => $pcid,
    ':aid' => $aid,
  ));
  foreach ($db_result as $item) {
    $result->status = TRUE;
    $result->nid = $nid;
    $result->id = $id;
    $result->parent_id = $parent_id;
    $result->parent_values = unserialize($item->parent_values);
    $result->required = $item->required;
    drupal_json_output($result);
    return;
  }
  $result->status = FALSE;
  drupal_json_output($result);
}

/**
 * Create an attribute dependency.
 *
 * A public function that creates and stores an attribute dependency for a
 * product.
 *
 * @param int $nid
 *   Node ID.
 * @param int $aid
 *   Attribute ID of the dependent (child) attribute.
 * @param int $parent_aid
 *   Attribute ID of the parent attribute.
 * @param array $options
 *   Array of the Option IDs that trigger the dependent attribute.
 * @param bool $required
 *   TRUE if the dependent (child) attribute is required when it appears and
 *   FALSE if it is not required.
 */
function uc_dropdown_attributes_product_create_dependency($nid, $aid, $parent_aid, $options, $required) {
  $attribute = uc_attribute_load($aid);
  $dep = db_insert('uc_dropdown_attributes')
    ->fields(array(
    'nid' => $nid,
    'aid' => $aid,
    'parent_aid' => $parent_aid,
    'parent_values' => serialize($options),
    'required' => $required,
  ))
    ->execute();

  // Need to check to make sure attribute is not required all the time.
  $sql = 'SELECT nid, aid, required FROM {uc_product_attributes}
    WHERE nid=:nid && aid=:aid';
  $result = db_query($sql, array(
    ':nid' => $nid,
    ':aid' => $aid,
  ));
  foreach ($result as $item) {
    if ($item->required == 1) {
      $dep = db_update('uc_product_attributes')
        ->fields(array(
        'required' => 0,
      ))
        ->condition('nid', $item->nid)
        ->condition('aid', $item->aid)
        ->execute();
    }
  }
}

/**
 * Create an attribute dependency for product classes.
 *
 * A public function that creates and stores an attribute dependency for
 * product classes.
 *
 * @param int $pcid
 *   Product class ID.
 * @param int $aid
 *   Attribute ID of the dependent (child) attribute.
 * @param int $parent_aid
 *   Attribute ID of the parent attribute.
 * @param array $options
 *   Array of the Option IDs that trigger the dependent attribute.
 * @param bool $required
 *   TRUE if the dependent (child) attribute is required when it appears and
 *   FALSE if it is not required.
 */
function uc_dropdown_attributes_class_create_dependency($pcid, $aid, $parent_aid, $options, $required) {
  $attribute = uc_attribute_load($aid);
  $dep = db_insert('uc_dropdown_classes')
    ->fields(array(
    'pcid' => $pcid,
    'aid' => $aid,
    'parent_aid' => $parent_aid,
    'parent_values' => serialize($options),
    'required' => $required,
  ))
    ->execute();

  // Need to check to make sure attribute is not required all the time.
  $sql = 'SELECT pcid, aid, required FROM {uc_class_attributes}
    WHERE pcid=:pcid && aid=:aid';
  $result = db_query($sql, array(
    ':pcid' => $pcid,
    ':aid' => $aid,
  ));
  foreach ($result as $item) {
    if ($item->required == 1) {
      $dep = db_update('uc_class_attributes')
        ->fields(array(
        'required' => 0,
      ))
        ->condition('pcid', $item->pcid)
        ->condition('aid', $item->aid)
        ->execute();
    }
  }
}

/**
 * Implements hook_theme().
 */
function uc_dropdown_attributes_theme() {
  return array(
    'uc_dropdown_attributes_product' => array(
      'render element' => 'form',
    ),
    'uc_dropdown_attributes_class' => array(
      'render element' => 'form',
    ),
  );
}

/**
 * Retrieve product class from the node ID.
 *
 * @param int $nid
 *   Node id.
 *
 * @return string
 *   The type field from the node object.
 */
function uc_dropdown_attributes_get_type($nid) {
  $sql = 'SELECT type FROM {node} WHERE nid=:nid';
  $type = db_query($sql, array(
    ':nid' => $nid,
  ))
    ->fetchField();
  return $type;
}

/**
 * Retrieve whether dependencies are defined by node or class.
 *
 * @param int $nid
 *   Node id.
 *
 * @return string
 *   'node' for dependencies defined on the node level; 'class' for dependencies
 *   defined on the product class; otherwise, NULL.
 */
function uc_dropdown_attributes_dependency_type($nid) {
  $sql = 'SELECT COUNT(*) FROM {uc_dropdown_attributes} WHERE nid=:nid';
  $count = db_query($sql, array(
    ':nid' => $nid,
  ))
    ->fetchField();
  if ($count > 0) {
    return 'node';
  }
  $pcid = uc_dropdown_attributes_get_type($nid);
  $sql = 'SELECT COUNT(*) FROM {uc_dropdown_classes} WHERE pcid=:pcid';
  $count = db_query($sql, array(
    ':pcid' => $pcid,
  ))
    ->fetchField();
  if ($count > 0) {
    return 'class';
  }
  return NULL;
}

/**
 * Determine the correct theme to use for dependencies admin page.
 *
 * @return string
 *   theme to use or empty string if default theme.
 */
function uc_dropdown_attributes_admin_theme() {
  if (variable_get('node_admin_theme', 0) == 1) {
    return variable_get('admin_theme', '');
  }
  return '';
}

/**
 * Implements hook_form_FORM_ID_alter() for uc_order_edit_form().
 */
function uc_dropdown_attributes_form_uc_order_edit_form_alter(&$form, &$form_state) {
  if (isset($form_state['products_action']) && $form_state['products_action'] == 'add_product') {
    $product_type = $form['product_controls']['node']['#value']->type;
    if ($product_type == 'product_kit') {
      foreach ($form['product_controls']['sub_products'] as $nid => $product) {
        if (is_numeric($nid)) {
          $type = uc_dropdown_attributes_dependency_type($nid);
          if (!is_null($type)) {
            uc_dropdown_attributes_order_product_alter($nid, $form['product_controls']['sub_products'][$nid]['attributes'], $type);
            if (!in_array('_uc_dropdown_attributes_order_product_kit_build', $form['#after_build'])) {
              $form['#after_build'][] = '_uc_dropdown_attributes_order_product_kit_build';
            }
          }
        }
      }
    }
    else {
      $nid = $form['product_controls']['node']['#value']->nid;
      $type = uc_dropdown_attributes_dependency_type($nid);
      if (!is_null($type)) {
        uc_dropdown_attributes_order_product_alter($nid, $form['product_controls']['attributes'], $type);
        $form['nid'] = array(
          '#type' => 'hidden',
          '#value' => $nid,
        );
        switch ($type) {
          case 'node':
            $form['#after_build'][] = '_uc_dropdown_attributes_order_product_build';
            break;
          case 'class':
            $form['#after_build'][] = '_uc_dropdown_attributes_order_class_build';
            break;
        }
      }
    }
  }
}

/**
 * Alter products on oder page in preparation for drop down attributes.
 *
 * Adds the 'Please select' and removes the default value.  Ubercart does this
 * for required attributes but since these attributes can no longer be required
 * if the attributes are dependent then this reproduces the same thing.
 *
 * @param int $nid
 *   Node ID.
 * @param array $form_attributes
 *   Attributes part of the product form.
 * @param string $type
 *   'node' for dependencies defined on the node level; 'class' for dependencies
 *   defined on the product class.
 */
function uc_dropdown_attributes_order_product_alter($nid, &$form_attributes, $type) {
  switch ($type) {
    case 'node':
      $sql = 'SELECT aid, parent_aid, required FROM {uc_dropdown_attributes}
        WHERE nid=:nid';
      $attributes = db_query($sql, array(
        ':nid' => $nid,
      ));
      break;
    case 'class':
      $sql = 'SELECT aid, parent_aid, required FROM {uc_dropdown_classes}
        WHERE pcid=:pcid';
      $pcid = uc_dropdown_attributes_get_type($nid);
      $attributes = db_query($sql, array(
        ':pcid' => $pcid,
      ));
      break;
  }
  $parent_aids = array();
  foreach ($attributes as $attribute) {
    $parent_aids[$attribute->parent_aid] = $attribute->parent_aid;
    if (isset($form_attributes[$attribute->aid]['#options']) && count($form_attributes[$attribute->aid]['#options']) && $attribute->required) {
      switch ($form_attributes[$attribute->aid]['#type']) {
        case 'select':
          $form_attributes[$attribute->aid]['#options'] = array(
            '' => t('Please select'),
          ) + $form_attributes[$attribute->aid]['#options'];
          $form_attributes[$attribute->aid]['#default_value'] = '';
          break;
        case 'radios':
          $form_attributes[$attribute->aid]['#default_value'] = '';
          break;
        case 'checkboxes':
          $form_attributes[$attribute->aid]['#default_value'] = array();
          break;
      }
    }
  }
  foreach ($parent_aids as $aid) {
    $form_attributes[$aid]['#ajax'] = array(
      'callback' => 'uc_dropdown_attributes_order_ajax_callback',
      'wrapper' => $form_attributes['#id'],
    );
  }
}

/**
 * Ajax callback for order attribute selection form elements.
 */
function uc_dropdown_attributes_order_ajax_callback($form, $form_state) {
  if (in_array('sub_products', $form_state['triggering_element']['#parents'])) {

    // This is a product kit.
    $nid = $form_state['triggering_element']['#parents'][2];
    return $form['product_controls']['sub_products'][$nid]['attributes'];
  }
  return $form['product_controls']['attributes'];
}

/**
 * Form build for products for the order page.
 *
 * Callback for $form['#after_build'] for products. Adds the CSS to hide
 * the dependent attributes.
 */
function _uc_dropdown_attributes_order_product_build($form, &$form_state) {
  $nid = $form['nid']['#value'];
  $sql = 'SELECT aid, parent_aid, parent_values, required
    FROM {uc_dropdown_attributes} WHERE nid=:nid';
  $attributes = db_query($sql, array(
    ':nid' => $nid,
  ));
  if (isset($form_state['triggering_element'])) {
    $parents = $form_state['triggering_element']['#parents'];
    $parent_aid = $parents[count($parents) - 1];
    $parent_value = $form_state['triggering_element']['#value'];
    uc_dropdown_attributes_remove_values($parent_aid, $parent_value, $nid, 'node', $form_state['values']['product_controls']);
  }
  uc_dropdown_attributes_order_attribute_display($attributes, $form['product_controls']['attributes'], $form_state['values']['product_controls']['attributes']);
  return $form;
}

/**
 * Form build for classes for the order page.
 *
 * Callback for $form['#after_build'] for product classes.  Adds
 * the CSS to hide the dependent attributes.
 */
function _uc_dropdown_attributes_order_class_build($form, &$form_state) {
  $sql = 'SELECT aid, parent_aid, parent_values, required
    FROM {uc_dropdown_classes} WHERE pcid=:pcid';
  $pcid = uc_dropdown_attributes_get_type($form['nid']['#value']);
  $attributes = db_query($sql, array(
    ':pcid' => $pcid,
  ));
  if (isset($form_state['triggering_element'])) {
    $parents = $form_state['triggering_element']['#parents'];
    $parent_aid = $parents[count($parents) - 1];
    $parent_value = $form_state['triggering_element']['#value'];

    // Note that values are stored in input, not values.
    uc_dropdown_attributes_remove_values($parent_aid, $parent_value, $pcid, 'class', $form_state['input']['product_controls']);
  }
  uc_dropdown_attributes_order_attribute_display($attributes, $form['product_controls']['attributes'], $form_state['values']['product_controls']['attributes']);
  return $form;
}

/**
 * Form build for product kits for the order page.
 *
 * Callback for $form['#after_build'] for products. Adds the CSS to hide
 * the dependent attributes.
 */
function _uc_dropdown_attributes_order_product_kit_build($form, &$form_state) {
  foreach ($form['product_controls']['sub_products'] as $nid => $product) {
    if (is_numeric($nid)) {
      $type = uc_dropdown_attributes_dependency_type($nid);
      switch ($type) {
        case 'node':
          $sql = 'SELECT aid, parent_aid, parent_values, required
            FROM {uc_dropdown_attributes} WHERE nid=:nid';
          $attributes = db_query($sql, array(
            ':nid' => $nid,
          ));
          if (isset($form_state['triggering_element']) && $nid == $form_state['triggering_element']['#parents'][2]) {
            $parents = $form_state['triggering_element']['#parents'];
            $parent_aid = $parents[count($parents) - 1];
            $parent_value = $form_state['triggering_element']['#value'];
            uc_dropdown_attributes_remove_values($parent_aid, $parent_value, $nid, 'node', $form_state['values']['product_controls']['sub_products'][$nid]);
          }
          uc_dropdown_attributes_order_attribute_display($attributes, $form['product_controls']['sub_products'][$nid]['attributes'], $form_state['values']['product_controls']['sub_products'][$nid]['attributes']);
          break;
        case 'class':
          $sql = 'SELECT aid, parent_aid, parent_values, required
            FROM {uc_dropdown_classes} WHERE pcid=:pcid';
          $pcid = uc_dropdown_attributes_get_type($nid);
          $attributes = db_query($sql, array(
            ':pcid' => $pcid,
          ));
          if (isset($form_state['triggering_element']) && $nid == $form_state['triggering_element']['#parents'][2]) {
            $parents = $form_state['triggering_element']['#parents'];
            $parent_aid = $parents[count($parents) - 1];
            $parent_value = $form_state['triggering_element']['#value'];

            // Note that values are stored in input, not values.
            uc_dropdown_attributes_remove_values($parent_aid, $parent_value, $pcid, 'class', $form_state['input']['product_controls']['sub_products'][$nid]);
          }
          uc_dropdown_attributes_order_attribute_display($attributes, $form['product_controls']['sub_products'][$nid]['attributes'], $form_state['values']['product_controls']['sub_products'][$nid]['attributes']);
          break;
      }
    }
  }
  return $form;
}

/**
 * Alter display of attributes on the oder page.
 *
 * @param array $attributes
 *   Array of attribute dependency objects.
 * @param array &$form_attributes
 *   Attributes part of the product form.
 * @param array $form_state_attributes
 *   Attributes part of the product form_state.
 */
function uc_dropdown_attributes_order_attribute_display($attributes, &$form_attributes, $form_state_attributes) {
  foreach ($attributes as $attribute) {
    $parent_value = $form_state_attributes[$attribute->parent_aid];
    if ($parent_value) {

      // A value has been entered in parent attribute.
      $values = unserialize($attribute->parent_values);
      if (array_key_exists($parent_value, $values)) {

        // Show dependent attribute.
        if ($attribute->required) {
          $form_attributes[$attribute->aid]['#required'] = TRUE;
        }
      }
      else {

        // Hide dependent attribute.
        $form_attributes[$attribute->aid]['#post_render'][] = 'uc_dropdown_attributes_post_render';
        if ($attribute->required && $form_attributes[$attribute->aid]['#value'] != '') {
          $form_attributes[$attribute->aid]['#value'] = '';
        }
      }
    }
    else {
      $form_attributes[$attribute->aid]['#post_render'][] = 'uc_dropdown_attributes_post_render';
      if ($attribute->required && $form_attributes[$attribute->aid]['#value'] != '') {
        $form_attributes[$attribute->aid]['#value'] = '';
      }
    }
  }
}

/**
 * Form build for classes in product kits for the order page.
 *
 * Callback for $form['#after_build'] for product classes.  Adds
 * the CSS to hide the dependent attributes.
 */
function _uc_dropdown_attributes_order_class_kit_build($form, &$form_state) {
  $sql = 'SELECT aid, parent_aid, parent_values, required
    FROM {uc_dropdown_classes} WHERE pcid=:pcid';
  $pcid = uc_dropdown_attributes_get_type($form['nid']['#value']);
  $attributes = db_query($sql, array(
    ':pcid' => $pcid,
  ));
  if (isset($form_state['triggering_element'])) {
    $parents = $form_state['triggering_element']['#parents'];
    $parent_aid = $parents[count($parents) - 1];
    $parent_value = $form_state['triggering_element']['#value'];
    uc_dropdown_attributes_remove_values($parent_aid, $parent_value, $pcid, 'class', $form_state['values']['product_controls']);
  }
  foreach ($attributes as $attribute) {
    $parent_value = $form_state['values']['product_controls']['attributes'][$attribute->parent_aid];
    if ($parent_value) {

      // A value has been entered in parent attribute.
      $values = unserialize($attribute->parent_values);
      if (array_key_exists($parent_value, $values)) {

        // Show dependent attribute.
        if ($attribute->required) {
          $form['product_controls']['attributes'][$attribute->aid]['#required'] = TRUE;
        }
      }
      else {

        // Hide dependent attribute.
        $form['product_controls']['attributes'][$attribute->aid]['#post_render'][] = 'uc_dropdown_attributes_post_render';
        if ($form['product_controls']['attributes'][$attribute->aid]['#required'] && $form['product_controls']['attributes'][$attribute->aid]['#value'] != '') {
          $form['product_controls']['attributes'][$attribute->aid]['#value'] = '';
        }
      }
    }
    else {
      $form['product_controls']['attributes'][$attribute->aid]['#post_render'][] = 'uc_dropdown_attributes_post_render';
      if ($form['product_controls']['attributes'][$attribute->aid]['#required'] && $form['product_controls']['attributes'][$attribute->aid]['#value'] != '') {
        $form['product_controls']['attributes'][$attribute->aid]['#value'] = '';
      }
    }
  }
  return $form;
}

/**
 * Implements hook_node_load().
 *
 * Ubercart fails to load attributes for product classes.
 */
function uc_dropdown_attributes_node_load($nodes) {
  foreach ($nodes as $node) {
    if (uc_product_is_product($node)) {
      if (empty($node->attributes)) {
        $node->attributes = uc_class_get_attributes($node->type);
      }
    }
  }
}

Functions

Namesort descending Description
uc_dropdown_attributes_admin_theme Determine the correct theme to use for dependencies admin page.
uc_dropdown_attributes_ajax_callback Ajax callback for attribute selection form elements.
uc_dropdown_attributes_class_create_dependency Create an attribute dependency for product classes.
uc_dropdown_attributes_clear_input Clear inputs for attribute.
uc_dropdown_attributes_dependency Retrieves the attribute dependencies.
uc_dropdown_attributes_dependency_type Retrieve whether dependencies are defined by node or class.
uc_dropdown_attributes_form_alter Implements hook_form_alter().
uc_dropdown_attributes_form_uc_order_edit_form_alter Implements hook_form_FORM_ID_alter() for uc_order_edit_form().
uc_dropdown_attributes_get_type Retrieve product class from the node ID.
uc_dropdown_attributes_menu Implements hook_menu().
uc_dropdown_attributes_node_load Implements hook_node_load().
uc_dropdown_attributes_order_ajax_callback Ajax callback for order attribute selection form elements.
uc_dropdown_attributes_order_attribute_display Alter display of attributes on the oder page.
uc_dropdown_attributes_order_product_alter Alter products on oder page in preparation for drop down attributes.
uc_dropdown_attributes_post_render Add the style to hide the attribute.
uc_dropdown_attributes_product_alter Alter products in preparation for drop down attributes.
uc_dropdown_attributes_product_create_dependency Create an attribute dependency.
uc_dropdown_attributes_remove_values Unset values of orphaned children.
uc_dropdown_attributes_theme Implements hook_theme().
_uc_dropdown_attributes_class_build Form build for classes.
_uc_dropdown_attributes_kit_build Form build for product kits.
_uc_dropdown_attributes_order_class_build Form build for classes for the order page.
_uc_dropdown_attributes_order_class_kit_build Form build for classes in product kits for the order page.
_uc_dropdown_attributes_order_product_build Form build for products for the order page.
_uc_dropdown_attributes_order_product_kit_build Form build for product kits for the order page.
_uc_dropdown_attributes_product_build Form build for products.