You are here

conditional_fields.module in Conditional Fields 6.2

Content fields and groups visibility based on the values of user defined 'trigger' fields.

File

conditional_fields.module
View source
<?php

/**
 * @file
 * Content fields and groups visibility based on the values of user defined 'trigger' fields.
 */

// Fields settings
define('C_FIELDS_JS_NO', 0);
define('C_FIELDS_JS_HIDE', 1);
define('C_FIELDS_JS_DISABLE', 2);
define('C_FIELDS_ANIMATION_NO', 0);
define('C_FIELDS_ANIMATION_FADE', 1);
define('C_FIELDS_ANIMATION_SLIDE', 2);
define('C_FIELDS_ORPHANED_HIDE', 0);
define('C_FIELDS_ORPHANED_SHOW_TRIGGERED', 1);
define('C_FIELDS_ORPHANED_SHOW_ALL', 2);
function conditional_fields_help($path, $arg) {
  switch ($path) {
    case "admin/content/node-type/{$arg[3]}/conditional":
      return t('These settings only apply to the conditional fields of this content type.');
      break;
    case 'admin/help#conditional_fields':
      $output = '<p>' . t('The Conditional Fields module allows to set fields with allowed values as "controlling fields" for other fields and groups. When a field or group is "controlled", it will only be available for editing and displayed if the selected values of the controlling field match the "trigger values" assigned to it. You can, for example, make a custom "article teaser" field that is shown only if a "Has teaser" checkbox is checked.') . '</p>';
      $output .= '<p>' . t('When editing a node, the controlled fields are dynamically shown and hidden with javascript.') . '</p>';
      $output .= '<p>' . t('On node view, the controlled fields which were left untriggered are hidden.') . '</p>';
      $output .= '<p>' . t('Once the module is activated, a new set of options will appear in the editing form of cck fields, from where you can select which of the allowed values available of candidate "controlling" fields will make the field "controlled". If <em>- Not controlling -</em> or no value is selected, the field will be shown as usual.') . '</p>';
      $output .= '<p>' . t('These are the requisites to make a field controllable:') . '</p>';
      $output .= '<ul>';
      $output .= '<li>' . t('The controlling field widget must be single or multiple select list, radio buttons or checkboxes.') . '</li>';
      $output .= '<li>' . t('The controlling field must have allowed values.') . '</li>';
      $output .= '<li>' . t('If the controlled field is in a group, the controlling field must be in the same group.') . '</li>';
      $output .= '<li>' . t('If you want to make a group controllable, the controlling field should not be inside a group.') . '</li>';
      $output .= '</ul>';
      $output .= '<p>' . t('There is also a "Conditional fields" settings tab in every content type admin page.') . '</p>';
      $output .= '<p>' . t('The <a href="@handbook">Conditional Fields Handbook</a> contains further explanations and examples.', array(
        '@handbook' => url('http://drupal.org/node/475488'),
      )) . '</p>';
      return $output;
      break;
  }
}

/**
 * Implementation of hook_menu().
 */
function conditional_fields_menu() {
  $items = array();
  foreach (node_get_types() as $type) {
    $content_type = content_types($type->type);
    $items['admin/content/node-type/' . $content_type['url_str'] . '/conditional'] = array(
      'title' => 'Conditional fields',
      'page callback' => 'drupal_get_form',
      'page arguments' => array(
        '_conditional_fields_admin',
        $content_type['type'],
      ),
      'access arguments' => array(
        'administer conditional fields',
      ),
      'type' => MENU_LOCAL_TASK,
      'weight' => 5,
    );
  }
  return $items;
}

/**
 * Implementation of hook_perm().
 */
function conditional_fields_perm() {
  return array(
    'administer conditional fields',
  );
}

/**
 *  Administration form for conditional fields
 */
function _conditional_fields_admin($form, $type) {
  $form = array();
  $form['js_set'] = array(
    '#type' => 'fieldset',
    '#title' => t('User Interface options'),
    '#collapsible' => TRUE,
  );
  $form['js_set']['js'] = array(
    '#type' => 'radios',
    '#options' => array(
      C_FIELDS_JS_NO => t("Don't use javascript. Fields are only hidden on node view."),
      C_FIELDS_JS_HIDE => t('Hide untriggered fields.'),
      C_FIELDS_JS_DISABLE => t('Disable untriggered fields.'),
    ),
    '#title' => 'Javascript',
    '#description' => t('Choose the desired javascript behaviour in node editing forms.'),
    '#default_value' => variable_get('c_fields_js_' . $type, C_FIELDS_JS_HIDE),
  );
  $form['js_set']['anim'] = array(
    '#type' => 'fieldset',
    '#title' => t('Animation'),
    '#description' => t('These settings have effect only if you select the "Hide untriggered fields" option above.'),
  );
  $form['js_set']['anim']['animation'] = array(
    '#type' => 'radios',
    '#title' => t('Type'),
    '#default_value' => variable_get('c_fields_animation_' . $type, C_FIELDS_ANIMATION_NO),
    '#options' => array(
      C_FIELDS_ANIMATION_NO => t('No animation'),
      C_FIELDS_ANIMATION_FADE => t('Fade'),
      C_FIELDS_ANIMATION_SLIDE => t('Slide down'),
    ),
  );
  $form['js_set']['anim']['anim_speed'] = array(
    '#type' => 'radios',
    '#title' => t('Speed'),
    '#description' => t('The speed at which the animation is performed. Slow = 600ms; Normal = 400ms; Fast = 200ms.'),
    '#default_value' => variable_get('c_fields_anim_speed_' . $type, 'normal'),
    '#options' => array(
      'slow' => t('Slow'),
      'normal' => t('Normal'),
      'fast' => t('Fast'),
    ),
  );
  $form['orphaned'] = array(
    '#type' => 'fieldset',
    '#title' => t('Orphaned controlled fields settings'),
    '#description' => t('Configure the visibility/editability of controlled fields whose controlling fields are not visible/editable.'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
  );
  $options = array(
    C_FIELDS_ORPHANED_HIDE => t('Hide'),
    C_FIELDS_ORPHANED_SHOW_TRIGGERED => t('Show only if triggered'),
    C_FIELDS_ORPHANED_SHOW_ALL => t('Show'),
  );
  $form['orphaned']['orphaned_view'] = array(
    '#type' => 'radios',
    '#title' => t('On node view'),
    '#options' => $options,
    '#default_value' => variable_get('c_fields_view_' . $type, C_FIELDS_ORPHANED_SHOW_TRIGGERED),
  );
  $form['orphaned']['orphaned_edit'] = array(
    '#type' => 'radios',
    '#title' => t('On node edit'),
    '#options' => $options,
    '#default_value' => variable_get('c_fields_edit_' . $type, C_FIELDS_ORPHANED_SHOW_TRIGGERED),
  );
  $form['reset_default'] = array(
    '#type' => 'checkbox',
    '#title' => t('Reset untriggered fields to default values'),
    '#description' => t('Select this box to reset untriggered controlled fields to their default values when saving a node.'),
    '#default_value' => variable_get('c_fields_reset_default_' . $type, 1),
  );
  $form['show_all'] = array(
    '#type' => 'checkbox',
    '#title' => t('Administrators see all fields'),
    '#description' => t('Select this box to always show controlled fields to users with the <a href="@access-control-page">administer conditional fields</a> permission when viewing a node.', array(
      '@access-control-page' => url('admin/user/permissions', array(
        'fragment' => 'module-conditional_fields',
      )),
    )),
    '#default_value' => variable_get('c_fields_show_all_' . $type, 0),
  );
  $form['delete'] = array(
    '#type' => 'checkbox',
    '#title' => t('Delete'),
    '#description' => t('Delete all conditional fields configured for this content type. This will delete the conditional fields settings, not the fields themselves.'),
    '#default_value' => 0,
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save'),
  );
  return $form;
}
function _conditional_fields_admin_submit($form, &$form_state) {
  $type = $form['#parameters'][2];
  variable_set('c_fields_js_' . $type, $form_state['values']['js']);
  variable_set('c_fields_animation_' . $type, $form_state['values']['animation']);
  variable_set('c_fields_anim_speed_' . $type, $form_state['values']['anim_speed']);
  variable_set('c_fields_view_' . $type, $form_state['values']['orphaned_view']);
  variable_set('c_fields_edit_' . $type, $form_state['values']['orphaned_edit']);
  variable_set('c_fields_reset_default_' . $type, $form_state['values']['reset_default']);
  variable_set('c_fields_show_all_' . $type, $form_state['values']['show_all']);
  drupal_set_message(t('Conditional fields options for this content type saved.') . $message);
  if ($form_state['values']['delete'] == 1) {
    conditional_fields_node_type_delete($type);
    drupal_set_message(t('All configured conditional fields for this content type have been deleted.'));
  }
}

/**
 * Implementation of hook_nodeapi().
 */
function conditional_fields_nodeapi(&$node, $op, $teaser, $page) {
  if ($op == 'view') {

    // First we check if there any conditional fields in this node type
    $type = content_types($node->type);
    if (!($data = conditional_fields_load_data($type['type']))) {
      return;
    }

    // Then we check if user is an administrator and this content type
    // and has the show hidden fields pref enabled
    if (user_access('administer conditional fields') && variable_get('c_fields_show_all_' . $type['type'], 0)) {
      return;
    }
    foreach ($data as $field) {

      // We might have to look for the field in a group
      $controlled_group = conditional_fields_get_group($node->type, $field['field_name']);
      $controlling_group = conditional_fields_get_group($node->type, $field['control_field_name']);

      // The controlled field is not in a group and is not in the form for other reasons. Skip.
      if (!$controlled_group && !$node->content[$field['field_name']]) {
        continue;
      }

      // The controlled field is in a group and is not the form for other reasons. Skip.
      if ($controlled_group && !$node->content[$controlled_group]['group'][$field['field_name']]) {
        continue;
      }
      $viewed = FALSE;
      $current_values[$field['control_field_name']] = array();

      // Create an array with the selected controlling field's values
      // Check if the controlling field is viewed as well
      $field_key = array_keys($type['fields'][$field['control_field_name']]['columns']);
      foreach ((array) $node->{$field}['control_field_name'] as $value) {
        $current_values[$field['control_field_name']][] = $value[$field_key[0]];
        if (!empty($value[$field_key[0]])) {
          if ($node->content[$field['control_field_name']]['field']['#access'] == TRUE || $controlling_group && $node->content[$controlling_group]['group'][$field['control_field_name']]['field']['#access'] == TRUE) {
            $viewed = TRUE;
          }
        }
      }
      if ($viewed) {

        // Hide the controlled field if it is not triggered
        if (!conditional_fields_is_triggered($current_values[$field['control_field_name']], $field['trigger_values'])) {
          if ($controlled_group) {
            $node->content[$controlled_group]['group'][$field['field_name']]['#access'] = FALSE;
          }
          else {
            $node->content[$field['field_name']]['#access'] = FALSE;
          }
        }
      }
      else {

        // Apply orphaned fields settings
        $orphaned_settings = variable_get('c_fields_view_' . $node->type, C_FIELDS_ORPHANED_SHOW_TRIGGERED);
        switch ($orphaned_settings) {
          case C_FIELDS_ORPHANED_SHOW_TRIGGERED:

            // If the field was triggered, don't hide it
            if (conditional_fields_is_triggered($current_values[$field['control_field_name']], $field['trigger_values'])) {
              break;
            }
          case C_FIELDS_ORPHANED_HIDE:

            // We hide the field
            if ($controlled_group) {
              $node->content[$controlled_group]['group'][$field['field_name']]['#access'] = FALSE;
            }
            else {
              $node->content[$field['field_name']]['#access'] = FALSE;
            }
          case C_FIELDS_ORPHANED_SHOW_ALL:

            // Nothing to do...
            break;
        }
      }
    }
  }
}

/**
 * Implementation of hook_form_alter().
 */
function conditional_fields_form_alter(&$form, $form_state, $form_id) {
  switch ($form_id) {
    case 'content_field_edit_form':
      if (!isset($form_state['clicked_button']) || $form_state['clicked_button']['#parents'][0] != 'change') {
        conditional_fields_content_admin_field($form);
      }
      break;
    case 'fieldgroup_group_edit_form':
      conditional_fields_fieldgroup_group_edit_form($form);
      break;
    case 'content_field_overview_form':

      // Find conditional fields, mark them, and disable group select for them
      $conditional_fields = conditional_fields_field_overview_form($form);
      break;
    case '_content_admin_field_remove':
      $form['#submit'] = $form['#submit'] + array(
        '_conditional_fields_content_admin_field_remove_submit' => array(),
      );
      break;
    case 'fieldgroup_remove_group':
      $form['#submit'][] = 'conditional_fields_fieldgroup_remove_group_submit';
      break;
    case 'content_add_more_js':

      // Handle ahah multiple fields
      foreach ($form as $item_name => $item) {
        if (db_result(db_query("SELECT COUNT(*) FROM {conditional_fields} WHERE field_name = '%s'", $item_name))) {
          $form[$item_name]['#post_render'] = array_merge(array(
            'conditional_fields_add_more_post_render',
          ), (array) $form[$item_name]['#post_render']);
          foreach (element_children($form[$item_name]) as $element) {
            conditional_fields_custom_required_field($form[$item_name][$element], $form['#field_info'][$item_name]);
          }
        }
      }
      break;
    case 'content_copy_import_form':
      $form['#submit'][] = 'conditional_fields_import';
      break;

    // Compatibility with Content Profile User Registration module.
    case 'user_register':
      if (isset($form['#content_profile_registration_use_types'])) {
        conditional_fields_node_form($form, $form_state);
      }
      break;
  }
  if (isset($form['type']['#value']) && $form_id == $form['type']['#value'] . '_node_form') {
    conditional_fields_node_form($form, $form_state);
  }
}

/**
 * Alteration of the field editing form
 */
function conditional_fields_content_admin_field(&$form) {
  if (!user_access('administer conditional fields')) {
    return;
  }
  $type = (array) content_types($form['type_name']['#value']);

  // Load conditional fields data.
  $data = conditional_fields_load_data($type['type']);

  // Get all fields controlled by this one.
  $controlled_fields = array();
  foreach ($data as $row) {
    if ($row['control_field_name'] == $form['field_name']['#value']) {
      $controlled_fields[$row['field_name']] = $row['trigger_values'];
    }
  }

  // Build controlling field form.
  if (!empty($controlled_fields)) {

    // Needed to handle nested fields.
    $form['#controlled_fields'] = $controlled_fields;

    // Add validation function
    $form['#validate'] = array_merge(array(
      'conditional_fields_content_admin_field_validate',
    ), $form['#validate']);
    $form['controlled_fields'] = array(
      '#type' => 'value',
      '#value' => $controlled_fields,
    );
    $controlled_fieldset = conditional_fields_content_admin_field_controlling($type, $controlled_fields);
  }

  // Check if field is in a group
  if (isset($form['widget']['group'])) {
    $group_of_controlled_field = $form['widget']['group']['#value'];
  }

  // Get candidate controlling fields
  foreach ($type['fields'] as $field) {

    // - Exclude this one :)
    if ($field['field_name'] == $form['field_name']['#value']) {
      continue;
    }

    // - Exclude fields already controlled by this one
    if (isset($controlled_fields[$field['field_name']])) {
      continue;
    }

    // - Exclude fields without Allowed values
    if (!($allowed_values[$field['field_name']] = conditional_fields_allowed_values($field))) {
      continue;
    }

    // If a field is inside a controlled group, it can control or be controlled only by fields in the same group.
    // This is needed to avoid uncontrollable loops.
    // First we gather data about groups
    $group_of_candidate_field = conditional_fields_get_group($field['type_name'], $field['field_name']);
    if ($group_of_controlled_field || $group_of_candidate_field) {
      $group_of_controlled_field_is_controlled = FALSE;
      $group_of_candidate_field_is_controlled = FALSE;
      foreach ($data as $row) {
        if ($row['field_name'] == $group_of_controlled_field) {
          $group_of_controlled_field_is_controlled = TRUE;
        }
        elseif ($row['field_name'] == $group_of_candidate_field) {
          $group_of_candidate_field_is_controlled = TRUE;
        }
      }
    }

    // If the controlled field is in a group...
    if ($group_of_controlled_field) {

      // - Exclude candidates outside the controlled field's group if the group is controlled
      if (!$group_of_candidate_field && $group_of_controlled_field_is_controlled) {
        continue;
      }
      elseif ($group_of_candidate_field && $group_of_candidate_field != $group_of_controlled_field && ($group_of_candidate_field_is_controlled || $group_of_controlled_field_is_controlled)) {
        continue;
      }
    }
    else {

      // - Exclude fields in controlled groups
      if ($group_of_candidate_field && $group_of_candidate_field_is_controlled) {
        continue;
      }
    }
    $available_fields[$field['field_name']] = $field;
  }
  if (isset($available_fields)) {
    $default_values = conditional_fields_available_fields_default_values($form['field_name']['#value'], $available_fields);
    $available_fieldset = conditional_fields_content_admin_field_controllable($type, $default_values, $available_fields, $allowed_values, 'field');

    // Add validation function
    $form['#validate'] = array_merge(array(
      'conditional_fields_content_admin_field_validate',
    ), $form['#validate']);

    // Add submission function
    $form['#submit'] = array_merge(array(
      'conditional_fields_forms_submit',
    ), $form['#submit']);
  }

  // Add the fieldsets to the form
  if (isset($controlled_fieldset) || isset($available_fieldset)) {
    $form['widget']['conditional_fields'] = array(
      '#type' => 'fieldset',
      '#title' => t('Conditional fields settings'),
      '#tree' => TRUE,
      '#collapsible' => TRUE,
      '#collapsed' => empty($default_values),
      '#weight' => 8,
      '#attributes' => array(
        'id' => 'conditional-fields-settings',
      ),
    );
    if (isset($controlled_fieldset)) {
      $form['widget']['conditional_fields']['controlling_fields'] = $controlled_fieldset;
    }
    if (isset($available_fieldset)) {
      if (isset($controlled_fieldset)) {
        $available_fieldset['#description'] .= '<p><strong>' . t('Note: this field is already controlling other fields. If you make it controlled, its own controlled fields will be assigned the same settings.') . '</strong></p>';
      }
      $form['widget']['conditional_fields']['available_fields'] = $available_fieldset;
    }
  }
  return;
}

/**
 * Controlling field settings form
 * (just a table containing information about controlled fields)
 */
function conditional_fields_content_admin_field_controlling($type, $controlled_fields) {
  foreach ($controlled_fields as $field => $trigger_values) {
    if (strpos($field, 'group_') === 0) {

      // It's a group
      $rows[] = array(
        $field,
        implode($trigger_values, ', ') . ' (' . t('<a href="@edit-group">edit</a>', array(
          '@edit-group' => url('admin/content/node-type/' . $type['url_str'] . '/groups/' . $field, array(
            'fragment' => 'conditional-fields-settings',
          )),
        )) . ')',
        t('group'),
      );
    }
    else {

      // It's a field
      $rows[] = array(
        $field,
        implode($trigger_values, ', ') . ' (' . t('<a href="@edit-field">edit</a>', array(
          '@edit-field' => url('admin/content/node-type/' . $type['url_str'] . '/fields/' . $field, array(
            'fragment' => 'conditional-fields-settings',
          )),
        )) . ')',
        t('field'),
      );
    }
  }
  $output = array(
    '#type' => 'fieldset',
    '#title' => t('Controlled fields') . ' (' . count($rows) . ')',
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
    '#description' => '<p>' . t('Below are listed all fields and groups controlled by this field.') . '</p>',
    'controlled_fields_table' => array(
      '#value' => theme('table', array(
        t('Name'),
        t('Trigger values'),
        t('Type'),
      ), $rows),
    ),
  );
  return $output;
}

/**
 * Controllable field settings form
 */
function conditional_fields_content_admin_field_controllable($type, $default_values, $available_fields, $allowed_values, $context) {
  $output = array(
    '#type' => 'fieldset',
    '#title' => t('Controlling fields'),
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
    '#description' => '<p>' . t('Choose which allowed values of available controlling fields will trigger this @context, making it visible both in node editing and view. If the <em>- Not controlling -</em> option or no option is selected, the @context will be always visible. If the <em>- None (empty value) -</em> option is selected, the controlled field will be visible only when the controlling field has no value. Only fields with <em>Allowed values</em> set are available for control. If a field is inside a controlled group, it can control or be controlled only by fields in the same group.', array(
      '@context' => $context,
    )) . '</p>',
  );

  // Create selection lists
  foreach ($available_fields as $field) {
    $allowed_values[$field['field_name']] = array(
      'conditional_field_no_value' => t('- Not controlling -'),
    ) + $allowed_values[$field['field_name']];
    if (!empty($default_values[$field['field_name']])) {
      $default_value = $default_values[$field['field_name']];
    }
    else {
      $default_value = 'conditional_field_no_value';
    }
    $output[$field['field_name']] = array(
      '#type' => 'select',
      '#multiple' => TRUE,
      '#title' => $field['widget']['label'] . ' (' . $field['field_name'] . ')',
      // To do: set right url for groups
      '#description' => t('<a href="@edit-field">Edit the allowed values</a> of %field-name.', array(
        '@edit-field' => url('admin/content/node-type/' . $type['url_str'] . '/fields/' . $field['field_name'], array(
          'query' => 'destination=admin/content/node-type/' . arg(3) . '/' . arg(4) . '/' . arg(5),
        )),
        '%field-name' => $field['field_name'],
      )),
      '#options' => $allowed_values[$field['field_name']],
      '#default_value' => $default_value,
    );
  }
  return $output;
}

/**
 * Alteration of the fieldgroup editing form
 */
function conditional_fields_fieldgroup_group_edit_form(&$form) {
  if (!user_access('administer conditional fields')) {
    return;
  }

  // A group that contains fields controlling or controlled by fields in other groups or no groups at all,
  // cannot be a controlled group.
  if ($data = conditional_fields_load_data($form['#content_type']['type'])) {
    $groups = fieldgroup_groups($form['#content_type']['type']);
    $group = $groups[$form['group_name']['#default_value']];
    foreach ($data as $row) {
      if (isset($group['fields'][$row['field_name']]) xor isset($group['fields'][$row['control_field_name']])) {
        return;
      }
    }
  }

  // Find fields with allowed values which are not inside a group or are in the same group of this group.
  foreach ($form['#content_type']['fields'] as $field) {
    $in_group = fieldgroup_get_group($form['#content_type']['type'], $field['field_name']);
    if (!$in_group || $in_group == $form['parent']['#default_value']) {
      if ($allowed_values[$field['field_name']] = conditional_fields_allowed_values($field)) {
        $available_fields[$field['field_name']] = $field;
      }
    }
  }
  if (isset($available_fields)) {
    $default_values = conditional_fields_available_fields_default_values($form['group_name']['#default_value'], $available_fields);

    // Add the fieldset to the form
    $form['widget']['conditional_fields'] = array(
      '#type' => 'fieldset',
      '#title' => t('Conditional fields settings'),
      '#tree' => TRUE,
      '#collapsible' => TRUE,
      '#collapsed' => empty($default_values),
      '#weight' => 8,
      '#attributes' => array(
        'id' => 'conditional-fields-settings',
      ),
    );
    $form['widget']['conditional_fields']['available_fields'] = conditional_fields_content_admin_field_controllable($form['#content_type'], $default_values, $available_fields, $allowed_values, 'group');

    // Add validation function
    $form['#validate'][] = 'conditional_fields_content_admin_field_validate';

    // Add submission function
    $form['#submit'] = array_merge(array(
      'conditional_fields_forms_submit',
    ), $form['#submit']);
  }
  return;
}

/**
 * Check selection of values
 */
function conditional_fields_content_admin_field_validate($form, &$form_state) {
  if ($form_state['values']['conditional_fields']['available_fields']) {
    $count_selected = 0;
    foreach ($form_state['values']['conditional_fields']['available_fields'] as $available_field => $trigger_values) {

      // Disallow selecting "Not controlling" and values at the same time
      if (!empty($trigger_values['conditional_field_no_value']) && count($trigger_values) > 1) {
        form_set_error('conditional_fields][' . $available_field, t('You cannot select "Not controlling" and other values at the same time.'));
      }

      // Disallow selecting "None (empty value)" and values at the same time.
      if (isset($trigger_values['']) && count($trigger_values) > 1) {
        form_set_error('conditional_fields][' . $available_field, t('You cannot select "None (empty value)" and other values at the same time.'));
      }
    }
  }
  else {

    // Warn user on allowed values change
    if (!$GLOBALS['content_copy'] && !empty($form['#controlled_fields']) && $form_state['values']['allowed_values'] != $form_state['#field_info'][$form['values']['field_name']]['allowed_values']) {
      drupal_set_message(t('If you changed or removed any allowed value from the field, you might have to review its controlled fields and fieldgroups settings.'), 'warning');
    }
  }
}

/**
 * Handle saving of conditional field settings.
 * The controlled field can be either a field or a group
 */
function conditional_fields_forms_submit($form, &$form_state) {
  $controlled_field = isset($form_state['values']['field_name']) ? $form_state['values']['field_name'] : $form_state['values']['group_name'];
  $type = isset($form['#field']['type_name']) ? $form['#field']['type_name'] : $form['#content_type']['type'];
  conditional_fields_save_field($type, $controlled_field, $form_state['values']['conditional_fields']['available_fields']);

  // Fields already controlled should share the same settings of their controlling fields
  if (!empty($form_state['values']['controlled_fields'])) {
    foreach ($form_state['values']['controlled_fields'] as $field_name => $trigger_values) {
      conditional_fields_save_field($type, $field_name, $form_state['values']['conditional_fields']['available_fields']);
    }
  }
}

/**
 * Find the allowed values for a field
 */
function conditional_fields_allowed_values($field) {
  static $options;
  if (!isset($options[$field['field_name']])) {
    $function = $field['module'] . '_allowed_values';
    $options[$field['field_name']] = function_exists($function) ? $function($field) : content_allowed_values($field);
    if (empty($options[$field['field_name']])) {
      return FALSE;
    }

    // Add an empty allowed value to unrequired single value fields.
    if (!$field['required'] && !$field['multiple']) {
      $options[$field['field_name']] = array_merge(array(
        '' => t('- None (empty value) -'),
      ), $options[$field['field_name']]);
    }

    // Strip tags, since we use a select, not the real widget.
    foreach ($options[$field['field_name']] as $key => $label) {
      $options[$field['field_name']][$key] = strip_tags($label);
    }
  }
  return $options[$field['field_name']];
}

/**
 * Alter node form. We do it in after_build for compatibility
 * with non-core CCK widgets
 */
function conditional_fields_node_form(&$form, $form_state) {
  $form['#after_build'][] = 'conditional_fields_node_after_build';
}

/**
 * Main tasks:
 * - Create javascript settings
 * - Prepare custom validation for required controlled fields
 * - Assign a theme function to conditional fields
 * - Apply orphaned fields settings if applicable
 */
function conditional_fields_node_after_build($form, &$form_state) {

  // Avoid running twice when the form is rebuilt with AHAH
  if (!empty($form_state['clicked_button']['#ahah'])) {
    return $form;
  }
  $type_name = $form['type']['#value'];

  // Do nothing if there are no conditional fields
  if (!($data = conditional_fields_load_data($type_name))) {
    return $form;
  }
  $controlling_fields = array();
  $missing_controlling_fields = array();
  $controlled_fields = array();
  $required_fields = array();
  $js_settings = array();
  foreach ($data as $row) {
    $controlling_fields[$row['control_field_name']][$row['field_name']] = $row['trigger_values'];
    $controlled_fields[$row['field_name']][$row['control_field_name']] = $row['trigger_values'];
  }

  /* Handle controlling fields */
  foreach ($controlling_fields as $controlling_field_name => $controlling_field_descendants) {

    // Check if the controlling field is in the form, user has access to it, and is editable.
    $group_of_controlling_field = conditional_fields_get_group($type_name, $controlling_field_name);
    $controlling_field = conditional_fields_item_in_form($form, $controlling_field_name, $group_of_controlling_field);
    if (!$controlling_field || $controlling_field['#access'] === FALSE || $controlling_field['#type'] == 'markup') {
      $missing_controlling_fields[] = $controlling_field_name;
      continue;
    }

    // Set values on form for themeing.
    if ($group_of_controlling_field) {
      $form[$group_of_controlling_field][$controlling_field_name]['#controlling_fields'] = TRUE;
      conditional_fields_item_apply_theme($form[$group_of_controlling_field][$controlling_field_name]);
    }
    else {
      $form[$controlling_field_name]['#controlling_fields'] = TRUE;
      conditional_fields_item_apply_theme($form[$controlling_field_name]);
    }
  }

  /* Handle controlled fields */
  foreach ($controlled_fields as $controlled_field_name => $controlled_field_parents) {

    // Check if the controlled field is in the form and user has access to it.
    $group_of_controlled_field = conditional_fields_get_group($type_name, $controlled_field_name);
    $controlled_field = conditional_fields_item_in_form($form, $controlled_field_name, $group_of_controlled_field);
    if (!$controlled_field || isset($controlled_field['#access']) && $controlled_field['#access'] === FALSE) {
      continue;
    }

    // Handle orphaned fields.
    foreach ($missing_controlling_fields as $missing_controlling_field) {
      unset($controlled_field_parents[$missing_controlling_field]);
    }
    if (count($controlled_field_parents) == 0) {
      $orphaned_settings = variable_get('c_fields_edit_' . $type_name, C_FIELDS_ORPHANED_SHOW_TRIGGERED);
      switch ($orphaned_settings) {
        case C_FIELDS_ORPHANED_SHOW_TRIGGERED:

          // Show only triggered fields. E.g.: fields whose controlling
          // fields have triggering values set by default, or set by
          // another user with permissions.
          $triggered = TRUE;
          foreach ($controlling_fields as $controlling_field_name => $controlling_field_descendants) {
            if ($controlling_field_descendants[$controlled_field_name]) {
              if (!conditional_fields_is_triggered($form_state['values'][$controlling_field_name], $controlling_field_descendants[$controlled_field_name])) {
                $triggered = FALSE;
                break;
              }
            }
          }
          if ($triggered) {
            break;
          }
        case C_FIELDS_ORPHANED_HIDE:

          // Unset controlled field.
          if ($group_of_controlled_field) {
            unset($form[$group_of_controlled_field][$controlled_field_name]);
          }
          else {
            unset($form[$controlled_field_name]);
          }
          break;
        case C_FIELDS_ORPHANED_SHOW_ALL:
      }
      continue;
    }
    if ($controlled_field['#required']) {
      $required_fields[$controlled_field_name] = array(
        'field' => $controlled_field_name,
        'in_group' => $group_of_controlled_field,
      );
    }
    $is_group = strpos($controlled_field_name, 'group_') === 0 ? TRUE : FALSE;

    // Required fields inside a controlled fieldgroup
    // must be handled by conditional fields.
    if ($is_group) {
      foreach (element_children($controlled_field) as $group_element) {
        if ($controlled_field[$group_element]['#required'] && !$controlled_fields[$group_element]) {
          $required_fields[$group_element] = array(
            'field' => $group_element,
            'in_group' => $controlled_field_name,
          );
        }
      }
    }

    // Set values on form for themeing.
    if ($group_of_controlled_field) {
      $form[$group_of_controlled_field][$controlled_field_name]['#controlled_fields'] = TRUE;
      conditional_fields_item_apply_theme($form[$group_of_controlled_field][$controlled_field_name]);
    }
    else {
      $form[$controlled_field_name]['#controlled_fields'] = TRUE;
      $is_group ? conditional_fields_item_apply_theme($form[$controlled_field_name], $controlled_field_name) : conditional_fields_item_apply_theme($form[$controlled_field_name]);
    }

    // Add fields to javascript settings
    // TODO: Use unique ids (requires per-widget settings)
    $js_controlled_field_id = '#conditional-' . conditional_fields_form_clean_id($controlled_field_name);
    foreach ($controlled_field_parents as $controlling_field_name => $trigger_values) {
      $js_controlling_field_id = '#conditional-' . conditional_fields_form_clean_id($controlling_field_name);
      $js_settings['controlling_fields'][$js_controlling_field_id][$js_controlled_field_id] = array(
        'field_id' => $js_controlled_field_id,
        'trigger_values' => $trigger_values,
      );
    }
  }

  // Controlled fields should only be required when triggered.
  // Since required fields validation is hardcoded in _form_validate,
  // we need to unset the #required property and perform a custom validation.
  foreach ($required_fields as $field) {
    if ($field['in_group']) {
      conditional_fields_custom_required_field($form[$field['in_group']][$field['field']], $form['#field_info'][$field['field']]);
      conditional_fields_item_apply_theme($form[$field['in_group']][$field['field']]);
    }
    else {
      conditional_fields_custom_required_field($form[$field['field']], $form['#field_info'][$field['field']]);
      conditional_fields_item_apply_theme($form[$field['field']]);
    }
  }

  // Apply user interface settings
  $ui_settings = variable_get('c_fields_js_' . $type_name, C_FIELDS_JS_HIDE);
  switch ($ui_settings) {
    case C_FIELDS_JS_DISABLE:
      $js_settings['ui_settings'] = 'disable';
      break;
    case C_FIELDS_JS_HIDE:
      $js_settings['ui_settings']['animation'] = (int) variable_get('c_fields_animation_' . $type_name, C_FIELDS_ANIMATION_NO);
      $js_settings['ui_settings']['anim_speed'] = variable_get('c_fields_anim_speed_' . $type_name, "normal");
      break;
  }

  // Key settings by an unique identifier so we can have multiple node forms
  // with conditional fields in the same page. We use the form build id since
  // the form's id might not be unique.
  $js_id = $form['#build_id'];
  $js_settings = array(
    $js_id => $js_settings,
  );
  if ($ui_settings != C_FIELDS_JS_NO) {
    conditional_fields_add_js($js_settings);
  }

  // Pass variables for validation
  $form['#conditional_fields']['data'] = $data;
  $form['#conditional_fields']['required_fields'] = $required_fields;
  $form['#conditional_fields']['settings'] = $js_settings;

  // Add validation function
  $form['#validate'] = array_merge(array(
    'conditional_fields_node_form_validate',
  ), (array) $form['#validate']);
  return $form;
}

/**
 * Insert appropriate themeing functions in a conditional field form element.
 */
function conditional_fields_item_apply_theme(&$element, $group_name = '') {
  if (!empty($group_name)) {
    $element['#group_id'] = 'conditional-' . conditional_fields_form_clean_id($group_name);

    // We add themeing in post_render so the wrapping is outside the fieldset.
    $post_render = isset($element['#post_render']) && is_array($element['#post_render']) ? $element['#post_render'] : array();
    $element['#post_render'] = array_merge(array(
      'conditional_fields_fieldgroup_post_render',
    ), $post_render);
  }
  else {

    // Save a previously set function so it can be called before rendering the field
    if (!empty($element['#theme']) && $element['#theme'] != 'conditional_fields_form_item') {
      $element['#conditional_fields_theme'] = $element['#theme'];
    }
    $element['#theme'] = 'conditional_fields_form_item';
  }
}

/**
 * Find an item in a form by key. If it's a CCK field, the function
 * will find it using field_info.
 */
function conditional_fields_item_in_form($form, $item_name, $group = FALSE) {
  static $items;
  if (!empty($items[$item_name])) {
    return $items[$item_name];
  }
  if ($group) {
    if (!empty($form[$group][$item_name])) {
      $items[$item_name] = $form[$group][$item_name];
    }
    elseif (!empty($form[$group][$form['#field_info'][$item_name]['display_settings']['parent']][$item_name])) {
      $items[$item_name] = $form[$group][$form['#field_info'][$item_name]['display_settings']['parent']][$item_name];
    }
  }
  else {
    if (!empty($form[$item_name])) {
      $items[$item_name] = $form[$item_name];
    }
    elseif (!empty($form[$form['#field_info'][$item_name]['display_settings']['parent']][$item_name])) {
      $items[$item_name] = $form[$form['#field_info'][$item_name]['display_settings']['parent']][$item_name];
    }
  }
  if (!empty($items[$item_name])) {
    return $items[$item_name];
  }
  return FALSE;
}

/**
 * Get the fieldgroup of a field
 */
function conditional_fields_get_group($type_name, $field_name) {
  if (!module_exists('fieldgroup')) {
    return FALSE;
  }
  return fieldgroup_get_group($type_name, $field_name);
}

/**
 * Validation for node editing form.
 */
function conditional_fields_node_form_validate($form, &$form_state) {

  // When form fails validation, hook_form_alter is not called, so we add js here too
  if ($form['#conditional_fields']['settings']['ui_settings']) {
    conditional_fields_add_js($form['#conditional_fields']['settings']);
  }

  // Rebuild the array grouping the data by controlled fields
  // Needed to handle multiple controlling field per controlled field
  $controlled_fields = array();
  foreach ($form['#conditional_fields']['data'] as $row) {
    $controlled_fields[$row['field_name']][$row['control_field_name']] = $row['trigger_values'];
  }
  foreach ($controlled_fields as $controlled_field_name => $controlling_fields) {

    // Check if all controlling field were triggered
    $triggered = FALSE;
    foreach ($controlling_fields as $controlling_field_name => $trigger_values) {
      $triggered = conditional_fields_is_triggered($form_state['values'][$controlling_field_name], $trigger_values);
      if ($triggered == FALSE) {
        break;
      }
    }
    $required_fields = $form['#conditional_fields']['required_fields'];
    if (!empty($required_fields[$controlled_field_name]['in_group'])) {
      $controlled_field =& $form[$required_fields[$controlled_field_name]['in_group']][$controlled_field_name];
    }
    else {
      $controlled_field =& $form[$controlled_field_name];
    }

    // Controlled field
    if (strpos($controlled_field_name, 'field_') === 0) {
      if ($triggered) {

        // Check required
        if (!empty($required_fields) && $required_fields[$controlled_field_name]) {
          $in_group = $required_fields[$controlled_field_name]['in_group'];

          // Check if the controlled field is empty
          if (conditional_fields_is_empty($form_state['values'][$controlled_field_name], $in_group ? $form[$in_group][$controlled_field_name] : $form[$controlled_field_name], $form['#field_info'][$controlled_field_name])) {

            // Check whether the controlled field is in a group or not and set error accordingly
            if (!$in_group) {
              form_error($controlled_field, t('!name field is required.', array(
                '!name' => $controlled_field['#title'],
              )));
            }
            else {

              // Set error only if the containing group is not controlled or is controlled and triggered
              $set_error = TRUE;
              foreach ($form['#conditional_fields']['data'] as $row_check_containing_group) {
                if ($row_check_containing_group['field_name'] == $in_group) {
                  if (!conditional_fields_is_triggered($form_state['values'][$row_check_containing_group['control_field_name']], $row_check_containing_group['trigger_values'])) {
                    $set_error = FALSE;
                  }
                  break;
                }
              }
              if ($set_error) {
                form_error($controlled_field, t('!name field is required.', array(
                  '!name' => $controlled_field['#title'],
                )));
              }
            }
          }
        }
      }
      else {

        // Do not submit values of controlled fields which were not triggered (except on preview)
        if (variable_get('c_fields_reset_default_' . $form['type']['#value'], 1) && !in_array('node_form_build_preview', (array) $form_state['submit_handlers'])) {

          // Load default values like in content_field_form() in content.node_form.inc
          $controlled_field_info = $form['#field_info'][$controlled_field_name];
          if (content_callback('widget', 'default value', $controlled_field_info) != CONTENT_CALLBACK_NONE) {
            $callback = content_callback('widget', 'default value', $controlled_field_info) == CONTENT_CALLBACK_CUSTOM ? $controlled_field_info['widget']['module'] . '_default_value' : 'content_default_value';
            if (function_exists($callback)) {
              $items = $callback($form, $form_state, $controlled_field_info, 0);
            }
          }
          $form_state['values'][$controlled_field_name] = $items;
        }
      }
    }
    elseif (strpos($controlled_field_name, 'group_') === 0) {
      foreach (element_children($controlled_field) as $field_in_group) {

        // Check if the controlling field was triggered
        if ($triggered) {

          // Check required
          if (!empty($required_fields) && $required_fields[$field_in_group] && !$controlled_field[$field_in_group]['#controlled_fields'] && conditional_fields_is_empty($form_state['values'][$field_in_group], $form[$controlled_field_name][$field_in_group], $form['#field_info'][$field_in_group])) {
            form_error($controlled_field[$field_in_group], t('!name field is required.', array(
              '!name' => $controlled_field[$field_in_group]['#title'],
            )));
          }
        }
        else {

          // Do not submit values of controlled fields which were not triggered (except on preview)
          if (variable_get('c_fields_reset_default_' . $form['type']['#value'], 1) && !isset($form_state['node_preview'])) {

            // Load default values like in content_field_form() in content.node_form.inc
            $field_in_group_info = $form['#field_info'][$field_in_group];
            if (content_callback('widget', 'default value', $field_in_group_info) != CONTENT_CALLBACK_NONE) {
              $callback = content_callback('widget', 'default value', $field_in_group_info) == CONTENT_CALLBACK_CUSTOM ? $field['widget']['module'] . '_default_value' : 'content_default_value';
              if (function_exists($callback)) {
                $items = $callback($form, $form_state, $field_in_group_info, 0);
              }
            }
            $form_state['values'][$field_in_group] = $items;
          }
        }
      }
    }
  }
}

/**
 * Checks if a submitted field value is empty.
 */
function conditional_fields_is_empty($item, $field, $field_info) {

  // First, check if the module that provides the field implements
  // hook_content_is_empty, and, if so, use it.
  if (in_array($field_info['module'], module_implements('content_is_empty'))) {
    $empty = $field_info['module'] . '_content_is_empty';
    return $empty($item, $field);
  }
  $value = NULL;

  // Attempt to extract the key from "_error_element".
  if (isset($item[0]['_error_element'])) {
    $error_tree = explode('][', $item[0]['_error_element']);
    $key = array_pop($error_tree);
    if (isset($item[0][$key])) {
      $value = $item[0][$key];
    }
  }

  // Last resort: maybe the field uses a generic "value" key.
  if (is_null($value) && isset($item[0]['value'])) {
    $value = $item[0]['value'];
  }
  else {

    // If no value was found, assume that the field is empty,
    // which may be annoying, but prevents saving incomplete data.
    return TRUE;
  }
  return !count($value) || is_string($value) && drupal_strlen(trim($value)) == 0;
}

/**
 * Returns true if the field was triggered
 * $selected_values The values of the controlling field selected by the user when creating the node
 * $trigger_values An array containing the information we need to select the trigger values
 */
function conditional_fields_is_triggered($selected_values, $trigger_values) {
  foreach ((array) $selected_values as $values) {
    foreach ((array) $values as $value) {
      if (isset($value) && in_array($value, $trigger_values)) {
        return TRUE;
      }
    }
  }
  return FALSE;
}

/**
 * Returns an array of conditional fields settings for a given node type.
 * $structure can be either 'flat' or 'row' . 'row' data is data per row,
 * while 'flat' data is a list of both controlling and controlled fields.
 */
function conditional_fields_load_data($type, $structure = 'row', $reset = FALSE) {
  static $data;
  if ($reset) {
    unset($data);
  }
  if (!isset($data[$structure][$type])) {
    $data[$structure][$type] = array();
    if ($structure == 'row') {
      $query = db_query("SELECT control_field_name, field_name, trigger_values FROM {conditional_fields} WHERE type = '%s'", $type);
      while ($result = db_fetch_array($query)) {
        $result['trigger_values'] = unserialize($result['trigger_values']);
        $data['row'][$type][] = $result;
      }
    }
    elseif ($structure == 'flat') {
      $query = db_query("SELECT control_field_name, field_name FROM {conditional_fields} WHERE type = '%s'", $type);
      while ($result = db_fetch_array($query)) {
        $data['flat'][$type][$result['control_field_name']] = $result['control_field_name'];
        $data['flat'][$type][$result['field_name']] = $result['field_name'];
      }
      $data['flat'][$type] = array_unique($data['flat'][$type]);
    }
  }
  return $data[$structure][$type];
}

/**
 * Find conditional fields and mark them.
 */
function conditional_fields_field_overview_form(&$form) {

  // Check if we have conditional data
  if (!($data = conditional_fields_load_data($form['#type_name']))) {
    return;
  }
  foreach ($data as $field) {
    if (in_array($field['control_field_name'], $form['#fields'])) {
      $form[$field['control_field_name']]['field_name']['#value'] .= theme('conditional_fields_manage_marker', NULL, $field['field_name']);
    }
    if (in_array($field['field_name'], $form['#fields'])) {
      $form[$field['field_name']]['field_name']['#value'] .= theme('conditional_fields_manage_marker', $field['control_field_name'], NULL);
    }

    // Mark groups
    if (in_array($field['field_name'], $form['#groups'])) {
      $form[$field['field_name']]['group_name']['#value'] .= theme('conditional_fields_manage_marker', $field['control_field_name'], NULL);
    }
  }

  // Add validation function
  $form['#validate'] = array_merge(array(
    'conditional_fields_field_overview_form_validate',
  ), $form['#validate']);
}

/**
 * Conditional fields in some situations can't change group.
 */
function conditional_fields_field_overview_form_validate($form, $form_state) {
  $data = conditional_fields_load_data($form['#type_name']);

  // Build a list of controlled fields/groups.
  $controlled_fields = array();
  foreach ($data as $dependency) {
    $controlled_fields[$dependency['field_name']] = $dependency['field_name'];
  }
  foreach ($data as $dependency) {

    // If a field is inside a controlled group, it can control or be controlled only by fields in the same group,
    // so moving fields in disallowed positions is forbidden.
    $controlling_group = $form_state['values'][$dependency['control_field_name']]['parent'];
    $controlled_group = $form_state['values'][$dependency['field_name']]['parent'];

    // If the fields share the same parent (no group or same group), allow moving.
    if ($controlling_group == $controlled_group) {
      continue;
    }

    // If the fields have different parents, only allow parents that are not controlled.
    if (in_array($controlling_group, $controlled_fields) || in_array($controlled_group, $controlled_fields)) {
      form_set_error('', t('Fields inside a controlled group can only be controlling or be controlled by a field in the same group.'));
    }
  }
}

/**
 * Load default values from conditional_fields table.
 */
function conditional_fields_available_fields_default_values($control_field, $conditional_fields) {
  $default_values = array();
  foreach ($conditional_fields as $field) {
    $query = db_query("SELECT trigger_values FROM {conditional_fields} WHERE control_field_name = '%s' AND field_name = '%s' AND type = '%s'", $field['field_name'], $control_field, $field['type_name']);
    $result = unserialize(db_result($query));
    if ($result) {
      $default_values[$field['field_name']] = $result;
    }
  }
  return $default_values;
}

/**
 * Adds javascript to the node editing form.
 *
 * This function ensures that the settings are added only once for each form
 * on the page that includes conditional fields.
 *
 * @param $settings
 *   An array of settings keyed by the form's #build_id property.
 */
function conditional_fields_add_js($settings) {
  static $js;
  $build_id = current(array_keys($settings));
  if (!isset($js[$build_id])) {
    drupal_add_js(array(
      'ConditionalFields' => $settings,
    ), 'setting');
    drupal_add_js(drupal_get_path('module', 'conditional_fields') . '/conditional_fields.js');
    $js[$build_id] = TRUE;
  }
}

/**
 * post_render function to handle controlled fields after "add more" ahah request
 */
function conditional_fields_add_more_post_render($content, $item) {
  return theme('conditional_fields_wrapper', $content, 'conditional-' . conditional_fields_form_clean_id($item['#field_name']), array(
    'conditional-field',
    ' controlled-field',
  ));
}

/**
 * post_render function to make fieldgroup wrapping themeable
 */
function conditional_fields_fieldgroup_post_render($content, $item) {
  return theme('conditional_fields_wrapper', $content, $item['#group_id'], array(
    'conditional-field',
    ' controlled-field',
  ));
}

/*
 * Clean conditional fields settings pertaining to this removed field
 */
function _conditional_fields_content_admin_field_remove_submit($form, $form_state) {
  conditional_fields_remove_field_settings($form_state['field_name']);
}

/*
 * Clean conditional fields settings pertaining to this removed group
 */
function conditional_fields_fieldgroup_remove_group_submit($form, $form_state) {
  conditional_fields_remove_field_settings($form['#group_name']);
}

/*
 * Remove all settings for a field.
 * Since our field names are really the field instance name, it should be safe to remove without checking.
 */
function conditional_fields_remove_field_settings($field_name) {
  db_query("DELETE FROM {conditional_fields} WHERE control_field_name = '%s' OR field_name = '%s'", $field_name, $field_name);
}

/**
 * Implementation of hook_node_type().
 */
function conditional_fields_node_type($op, $info) {
  switch ($op) {
    case 'update':
      conditional_fields_node_type_update($info);
      break;
    case 'delete':
      conditional_fields_node_type_delete($info->type);
      break;
  }
}

/**
 * Update conditional fields to a new type name
 */
function conditional_fields_node_type_update($info) {
  if (isset($info->old_type) && $info->type != $info->old_type) {
    db_query("UPDATE {conditional_fields} SET type = '%s' WHERE type ='%s'", $info->type, $info->old_type);

    // Update variables
    db_query("UPDATE {variable} SET name = 'c_fields_js_%s' WHERE name ='c_fields_js_%s'", $info->type, $info->old_type);
    db_query("UPDATE {variable} SET name = 'c_fields_animation_%s' WHERE name ='c_fields_animation_%s'", $info->type, $info->old_type);
    db_query("UPDATE {variable} SET name = 'c_fields_anim_speed_%s' WHERE name ='c_fields_anim_speed_%s'", $info->type, $info->old_type);
    db_query("UPDATE {variable} SET name = 'c_fields_reset_default_%s' WHERE name ='c_fields_reset_default_%s'", $info->type, $info->old_type);
    db_query("UPDATE {variable} SET name = 'c_fields_show_all_%s' WHERE name ='c_fields_show_all_%s'", $info->type, $info->old_type);
    db_query("UPDATE {variable} SET name = 'c_fields_view_%s' WHERE name ='c_fields_view_%s'", $info->type, $info->old_type);
    db_query("UPDATE {variable} SET name = 'c_fields_edit_%s' WHERE name ='c_fields_edit_%s'", $info->type, $info->old_type);
    cache_clear_all('variables', 'cache');
  }
}

/**
 * Remove conditional fields of a node type
 */
function conditional_fields_node_type_delete($type) {
  db_query("DELETE FROM {conditional_fields} WHERE type = '%s'", $type);

  // Delete variables
  variable_del('c_fields_js_' . $type);
  variable_del('c_fields_animation_' . $type);
  variable_del('c_fields_anim_speed_' . $type);
  variable_del('c_fields_reset_default_' . $type);
  variable_del('c_fields_show_all_' . $type);
  variable_del('c_fields_view_' . $type);
  variable_del('c_fields_edit_' . $type);
}

/**
 * Implementation of hook_content_fieldapi().
 */
function conditional_fields_content_fieldapi($ops, $field) {

  // Handle deletion of fields
  if ($ops == 'delete instance') {
    db_query("DELETE FROM {conditional_fields} WHERE type = '%s' AND (control_field_name = '%s' OR field_name = '%s')", $field['type_name'], $field['field_name'], $field['field_name']);
  }
  elseif ($ops == 'create instance') {
    if (!empty($field['conditional_fields']['available_fields'])) {
      conditional_fields_save_field($field['type_name'], $field['field_name'], $field['conditional_fields']['available_fields']);
    }
  }
}

/**
 * Handle saving of individual controlled field settings
 * $controlling_fields is a keyed array of controlling fields names and trigger values
 *
 * Use this function with caution, as it doesn't check if the content type and the fields actually exist.
 */
function conditional_fields_save_field($type_name, $controlled_field, $controlling_fields) {
  foreach ($controlling_fields as $controlling_field => $trigger_values) {

    // If the row already exists
    if (db_result(db_query("SELECT COUNT(*) FROM {conditional_fields} WHERE control_field_name = '%s' AND field_name = '%s' AND type = '%s'", $controlling_field, $controlled_field, $type_name))) {

      // If no value is set, delete the entry, else update it
      if (empty($trigger_values) || !empty($trigger_values['conditional_field_no_value'])) {
        conditional_fields_delete_field($type_name, $controlled_field, $controlling_field);
      }
      else {
        conditional_fields_update_field($type_name, $controlled_field, $controlling_field, $trigger_values);
      }
    }
    else {

      // If values are set, create new entry
      if (!empty($trigger_values) && empty($trigger_values['conditional_field_no_value'])) {
        conditional_fields_insert_field($type_name, $controlled_field, $controlling_field, $trigger_values);
      }
    }
  }
}

/**
 * Insert into database the data of a new conditional field
 */
function conditional_fields_insert_field($type_name, $controlled_field, $controlling_field, $trigger_values) {
  db_query("INSERT INTO {conditional_fields} (control_field_name, field_name, type, trigger_values) VALUES ('%s', '%s', '%s', '%s')", $controlling_field, $controlled_field, $type_name, serialize($trigger_values));
}

/**
 * Update an existing conditonal field's database data
 */
function conditional_fields_update_field($type_name, $controlled_field, $controlling_field, $trigger_values) {
  db_query("UPDATE {conditional_fields} SET trigger_values = '%s' WHERE control_field_name = '%s' AND field_name = '%s' AND type = '%s'", serialize($trigger_values), $controlling_field, $controlled_field, $type_name);
}

/**
 * Delete a conditonal field's database data
 */
function conditional_fields_delete_field($type_name, $controlled_field, $controlling_field) {
  db_query("DELETE FROM {conditional_fields} WHERE control_field_name = '%s' AND field_name = '%s' AND type = '%s'", $controlling_field, $controlled_field, $type_name);
}

/**
 * Unset the #required property and set a #conditional_fields_required property
 * for custom validation.
 *
 * @see conditional_fields_set_required_field()
 */
function conditional_fields_custom_required_field(&$field, &$field_info = NULL) {
  if (isset($field['#required']) && $field['#required']) {
    unset($field['#required']);
    $field['#conditional_fields_required'] = TRUE;

    // Some modules like FileField use the field info property to check if the
    // field is required, so change it.
    if ($field_info) {
      $field_info['required'] = '0';
    }

    // Required radio buttons without a default value need custom validation,
    // otherwise _form_validate will think that an invalid choice is selected
    // when the field is submitted with no value.
    if ($field['#type'] == 'radios' && !$field['#default_value'] && isset($field['#needs_validation']) && $field['#needs_validation']) {
      unset($field['#needs_validation']);
      $field['#element_validate'] = array_merge(array(
        'conditional_fields_required_radios_validate',
      ), (array) $field['#validate']);
    }
  }
  foreach (element_children($field) as $child) {
    conditional_fields_custom_required_field($field[$child]);
  }
}

/**
 * Custom validation for radio buttons.
 * Reproduces the behavior of _form_validate, while adding an empty option.
 */
function conditional_fields_required_radios_validate($elements) {
  $elements['#options'][''] = t('N/A');
  if (isset($elements['#options']) && isset($elements['#value'])) {
    if (is_array($elements['#value'])) {
      foreach ($elements['#value'] as $v) {
        if (!isset($elements['#options'][$v])) {
          form_error($elements, t('An illegal choice has been detected. Please contact the site administrator.'));
          watchdog('form', 'Illegal choice %choice in !name element.', array(
            '%choice' => $v,
            '!name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title'],
          ), WATCHDOG_ERROR);
        }
      }
    }
    elseif (!isset($elements['#options'][$elements['#value']])) {
      form_error($elements, t('An illegal choice has been detected. Please contact the site administrator.'));
      watchdog('form', 'Illegal choice %choice in %name element.', array(
        '%choice' => $elements['#value'],
        '%name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title'],
      ), WATCHDOG_ERROR);
    }
  }
}

/**
 * Marks as required a conditionally required form item and its descendants.
 *
 * Since this function is called from a theme function, the item will appear
 * required, but it won't be actually required.
 *
 * @see conditional_fields_custom_required_field()
 */
function conditional_fields_set_required_field($item) {
  if (!empty($item['#conditional_fields_required'])) {
    $item['#required'] = TRUE;
  }
  foreach (element_children($item) as $child) {
    $item[$child] = conditional_fields_set_required_field($item[$child]);
  }
  return $item;
}

/**
 * Implementation of hook_features_api() (features module).
 */
function conditional_fields_features_api() {
  return array(
    'conditional_fields' => array(
      'name' => t('Conditional Fields'),
      'default_hook' => 'conditional_fields_default_fields',
      'default_file' => FEATURES_DEFAULTS_INCLUDED,
      'features_source' => TRUE,
      'file' => drupal_get_path('module', 'conditional_fields') . '/includes/conditional_fields.features.inc',
    ),
  );
}

/**
 * Implementation of hook_theme().
 */
function conditional_fields_theme() {
  return array(
    'conditional_fields_form_item' => array(
      'arguments' => array(),
    ),
    'conditional_fields_wrapper' => array(
      'arguments' => array(),
    ),
    'conditional_fields_manage_marker' => array(
      'arguments' => array(
        'controlling' => NULL,
        'controlled' => NULL,
      ),
    ),
  );
}

/**
 * Prepares conditional fields for rendering and handles
 * special cases.
 * Note that if you modify the id and classes of these fields,
 * you have to modify conditional_fields.js accordingly.
 */
function theme_conditional_fields_form_item($item) {
  if (!empty($item['#conditional_fields_required'])) {
    $item = conditional_fields_set_required_field($item);
  }
  if (!empty($item['#conditional_fields_theme'])) {
    $rendered_item = theme($item['#conditional_fields_theme'], $item);
  }
  else {
    $rendered_item = drupal_render($item);
  }
  if (empty($item['#controlling_fields']) && empty($item['#controlled_fields'])) {
    return $rendered_item;
  }
  $id = 'conditional-' . conditional_fields_form_clean_id(!empty($item['#field_name']) ? $item['#field_name'] : $item['#array_parents'][0]);
  $classes = array(
    'conditional-field',
  );
  if (!empty($item['#controlled_fields'])) {
    $classes[] = 'controlled-field';
  }
  if (!empty($item['#controlling_fields'])) {
    $classes[] = 'controlling-field';
  }
  return theme('conditional_fields_wrapper', $rendered_item, $id, $classes);
}

/**
 * Themes a wrapper around conditional fields.
 * Note that if you modify the id and classes of these fields,
 * you have to modify conditional_fields.js accordingly.
 */
function theme_conditional_fields_wrapper($content, $id, $classes) {
  return '<div id="' . $id . '" class="' . implode(' ', $classes) . '">' . $content . '</div>';
}

/**
 * Themes a conditional field marker in 'Manage fields'.
 */
function theme_conditional_fields_manage_marker($controlling, $controlled) {
  $output = '';
  if ($controlling) {
    $output .= '<div class="description">' . t('Controlled by %controlling', array(
      '%controlling' => $controlling,
    )) . '</div>';
  }
  if ($controlled) {
    $output .= '<div class="description">' . t('Controlling %controlled', array(
      '%controlled' => $controlled,
    )) . '</div>';
  }
  return $output;
}

/**
 * form_clean_id in Drupal 6 adds a unique ID check, which messes things up here
 * Since fields have unique names anyway, we can safely use this wrapper (I hope).
 */
function conditional_fields_form_clean_id($id) {
  $id = str_replace(array(
    '][',
    '_',
    ' ',
  ), '-', $id);
  return $id;
}

Functions

Namesort descending Description
conditional_fields_add_js Adds javascript to the node editing form.
conditional_fields_add_more_post_render post_render function to handle controlled fields after "add more" ahah request
conditional_fields_allowed_values Find the allowed values for a field
conditional_fields_available_fields_default_values Load default values from conditional_fields table.
conditional_fields_content_admin_field Alteration of the field editing form
conditional_fields_content_admin_field_controllable Controllable field settings form
conditional_fields_content_admin_field_controlling Controlling field settings form (just a table containing information about controlled fields)
conditional_fields_content_admin_field_validate Check selection of values
conditional_fields_content_fieldapi Implementation of hook_content_fieldapi().
conditional_fields_custom_required_field Unset the #required property and set a #conditional_fields_required property for custom validation.
conditional_fields_delete_field Delete a conditonal field's database data
conditional_fields_features_api Implementation of hook_features_api() (features module).
conditional_fields_fieldgroup_group_edit_form Alteration of the fieldgroup editing form
conditional_fields_fieldgroup_post_render post_render function to make fieldgroup wrapping themeable
conditional_fields_fieldgroup_remove_group_submit
conditional_fields_field_overview_form Find conditional fields and mark them.
conditional_fields_field_overview_form_validate Conditional fields in some situations can't change group.
conditional_fields_forms_submit Handle saving of conditional field settings. The controlled field can be either a field or a group
conditional_fields_form_alter Implementation of hook_form_alter().
conditional_fields_form_clean_id form_clean_id in Drupal 6 adds a unique ID check, which messes things up here Since fields have unique names anyway, we can safely use this wrapper (I hope).
conditional_fields_get_group Get the fieldgroup of a field
conditional_fields_help
conditional_fields_insert_field Insert into database the data of a new conditional field
conditional_fields_is_empty Checks if a submitted field value is empty.
conditional_fields_is_triggered Returns true if the field was triggered $selected_values The values of the controlling field selected by the user when creating the node $trigger_values An array containing the information we need to select the trigger values
conditional_fields_item_apply_theme Insert appropriate themeing functions in a conditional field form element.
conditional_fields_item_in_form Find an item in a form by key. If it's a CCK field, the function will find it using field_info.
conditional_fields_load_data Returns an array of conditional fields settings for a given node type. $structure can be either 'flat' or 'row' . 'row' data is data per row, while 'flat' data is a list of both controlling and controlled fields.
conditional_fields_menu Implementation of hook_menu().
conditional_fields_nodeapi Implementation of hook_nodeapi().
conditional_fields_node_after_build Main tasks:
conditional_fields_node_form Alter node form. We do it in after_build for compatibility with non-core CCK widgets
conditional_fields_node_form_validate Validation for node editing form.
conditional_fields_node_type Implementation of hook_node_type().
conditional_fields_node_type_delete Remove conditional fields of a node type
conditional_fields_node_type_update Update conditional fields to a new type name
conditional_fields_perm Implementation of hook_perm().
conditional_fields_remove_field_settings
conditional_fields_required_radios_validate Custom validation for radio buttons. Reproduces the behavior of _form_validate, while adding an empty option.
conditional_fields_save_field Handle saving of individual controlled field settings $controlling_fields is a keyed array of controlling fields names and trigger values
conditional_fields_set_required_field Marks as required a conditionally required form item and its descendants.
conditional_fields_theme Implementation of hook_theme().
conditional_fields_update_field Update an existing conditonal field's database data
theme_conditional_fields_form_item Prepares conditional fields for rendering and handles special cases. Note that if you modify the id and classes of these fields, you have to modify conditional_fields.js accordingly.
theme_conditional_fields_manage_marker Themes a conditional field marker in 'Manage fields'.
theme_conditional_fields_wrapper Themes a wrapper around conditional fields. Note that if you modify the id and classes of these fields, you have to modify conditional_fields.js accordingly.
_conditional_fields_admin Administration form for conditional fields
_conditional_fields_admin_submit
_conditional_fields_content_admin_field_remove_submit

Constants