You are here

function conditional_fields_form_after_build in Conditional Fields 7.3

Same name and namespace in other branches
  1. 8 conditional_fields.module \conditional_fields_form_after_build()
  2. 4.x conditional_fields.module \conditional_fields_form_after_build()

after_build callback for forms with dependencies.

Builds and attaches #states properties to dependent fields, adds additional visual effects handling to the States API and attaches a validation callback to the form that handles validation of dependent fields.

1 string reference to 'conditional_fields_form_after_build'
conditional_fields_attach_dependency in ./conditional_fields.module
Attaches a single dependency to a form.

File

./conditional_fields.module, line 423
Define dependencies between fields based on their states and values.

Code

function conditional_fields_form_after_build($form, &$form_state) {

  // Dependencies data is attached in conditional_fields_element_after_build().
  if (empty($form['#conditional_fields'])) {
    return $form;
  }
  $effects = array();
  $state_handlers = conditional_fields_states_handlers();

  // Cycle all dependents.
  foreach ($form['#conditional_fields'] as $dependent => $dependent_info) {
    $states = array();
    if (empty($dependent_info['dependees'])) {
      continue;
    }
    $dependent_location = array_merge($dependent_info['field_parents'], array(
      $dependent,
    ));
    $dependent_form_field = drupal_array_get_nested_value($form, $dependent_location);

    // Cycle the dependant's dependees.
    foreach ($dependent_info['dependees'] as $dependency) {
      $dependee = $dependency['dependee'];
      if (empty($form['#conditional_fields'][$dependee])) {
        continue;
      }
      $dependee_info = $form['#conditional_fields'][$dependee];
      $dependee_form_field = drupal_array_get_nested_value($form, $dependee_info['parents']);
      $options = $dependency['options'];

      // Load field edit behaviors.
      // If this dependent has multiple dependees, only the logic of the first
      // dependency will be taken into account.
      if (!isset($behaviors)) {
        $behaviors = conditional_fields_field_behaviors('edit', $options);
      }

      // Determine if the dependee is in the form.
      if (empty($dependee_form_field) || isset($dependee_form_field['#access']) && $dependee_form_field['#access'] == FALSE) {

        // Apply orphan dependent behaviors.

        /*
        if (in_array(CONDITIONAL_FIELDS_FIELD_EDIT_HIDE_UNTRIGGERED_ORPHAN, $behaviors)) {
          // TODO
          $is_triggered = TRUE;

          if ($is_orphan && !$is_triggered) {
            $form[$dependent]['#access'] = FALSE;
          }
        }
        */
        if (in_array(CONDITIONAL_FIELDS_FIELD_EDIT_HIDE_ORPHAN, $behaviors)) {
          $dependent_form_field['#access'] = FALSE;
        }
        unset($behaviors[CONDITIONAL_FIELDS_FIELD_EDIT_HIDE_UNTRIGGERED_ORPHAN]);
        unset($behaviors[CONDITIONAL_FIELDS_FIELD_EDIT_HIDE_ORPHAN]);
        unset($behaviors[CONDITIONAL_FIELDS_FIELD_EDIT_RESET_UNTRIGGERED]);
        continue;
      }
      unset($behaviors[CONDITIONAL_FIELDS_FIELD_EDIT_HIDE_UNTRIGGERED_ORPHAN]);
      unset($behaviors[CONDITIONAL_FIELDS_FIELD_EDIT_HIDE_ORPHAN]);

      // Build a jQuery selector if it was not overridden by a custom value.
      // Note that this may be overridden later by a state handler.
      if (!$options['selector']) {
        $options['selector'] = conditional_fields_field_selector($dependee_form_field);
      }
      else {

        // Replace the language placeholder in the selector with current language.
        $options['selector'] = str_replace('%lang', $dependee_form_field['#language'], $options['selector']);
      }
      if ($options['condition'] != 'value') {

        // Conditions different than "value" are always evaluated against TRUE.
        $state = array(
          $options['state'] => array(
            $options['selector'] => array(
              $options['condition'] => TRUE,
            ),
          ),
        );
      }
      else {

        // Build the values that trigger the dependency.
        $values = array();
        if ($options['values_set'] == CONDITIONAL_FIELDS_DEPENDENCY_VALUES_WIDGET) {
          $values[$options['condition']] = $options['value_form'];
        }
        elseif ($options['values_set'] == CONDITIONAL_FIELDS_DEPENDENCY_VALUES_REGEX) {
          $values[$options['condition']] = $options['value'];
        }
        elseif ($options['values_set'] == CONDITIONAL_FIELDS_DEPENDENCY_VALUES_AND) {
          $values[$options['condition']] = count($options['values']) == 1 ? $options['values'][0] : $options['values'];
        }
        else {
          if ($options['values_set'] == CONDITIONAL_FIELDS_DEPENDENCY_VALUES_XOR) {

            // XOR behaves like OR with added 'xor' element.
            $values[] = 'xor';
          }
          elseif ($options['values_set'] == CONDITIONAL_FIELDS_DEPENDENCY_VALUES_NOT) {

            // NOT behaves like OR with switched state.
            $options['state'] = strpos($options['state'], '!') === 0 ? drupal_substr($options['state'], 1) : '!' . $options['state'];
          }

          // OR, NOT and XOR conditions are obtained with a nested array.
          foreach ($options['values'] as $value) {
            $values[] = array(
              $options['condition'] => $value,
            );
          }
        }
        $state = array(
          $options['state'] => array(
            $options['selector'] => $values,
          ),
        );
        $dependee_form_state = isset($dependee_form_field['#field_parents'], $dependee_form_field['#field_name'], $dependee_form_field['#language']) ? field_form_get_state($dependee_form_field['#field_parents'], $dependee_form_field['#field_name'], $dependee_form_field['#language'], $form_state) : NULL;

        // Execute special handler for fields that need further processing.
        // The handler has no return value. Modify the $state parameter by
        // reference if needed.
        foreach ($state_handlers as $handler => $handler_conditions) {
          if (array_intersect_assoc($handler_conditions, $dependee_form_field) == $handler_conditions) {
            $handler($dependee_form_field, $dependee_form_state, $options, $state);
          }
        }
      }

      // Add validation callback to element if the dependency can be evaluated.
      if (in_array($options['condition'], array(
        'value',
        'empty',
        '!empty',
      ))) {
        _conditional_fields_element_add_property($dependent_form_field, '#element_validate', 'conditional_fields_dependent_validate', 'append');
      }

      // Add the $state into the correct logic group in $states.
      foreach ($state as $key => $constraints) {
        foreach ($constraints as $selector => $constraint) {

          // Add the constraint in an array to avoid overwriting other
          // dependencies' states with the same selector.
          $states[$key][$options['grouping']][$selector][] = $constraint;
        }
      }

      // Build effect settings for effects with options.
      // TODO: add dependee key to allow different effects on the same selector.
      if ($options['effect'] && $options['effect'] != 'show') {
        $selector = conditional_fields_field_selector(drupal_array_get_nested_value($form, array(
          $dependent_location[0],
        )));

        // Convert numeric strings to numbers.
        foreach ($options['effect_options'] as &$effect_option) {
          if (is_numeric($effect_option)) {
            $effect_option += 0;
          }
        }
        $effects[$selector] = array(
          'effect' => $options['effect'],
          'options' => $options['effect_options'],
        );
      }

      // Apply reset dependent to default if untriggered behavior.
      if (in_array(CONDITIONAL_FIELDS_FIELD_EDIT_RESET_UNTRIGGERED, $behaviors)) {

        // Add property to element so conditional_fields_dependent_validate() can
        // pick it up.
        $dependent_form_field['#conditional_fields_reset_if_untriggered'] = TRUE;
        unset($behaviors[CONDITIONAL_FIELDS_FIELD_EDIT_RESET_UNTRIGGERED]);
      }
    }

    // Execute custom behaviors callbacks.
    if (!empty($behaviors)) {
      foreach ($behaviors as $behavior) {
        $behavior($form, $form_state, $dependent, $dependent_info);
      }
    }
    unset($behaviors);
    if (empty($states)) {
      continue;
    }

    // Save the modified field back into the form.
    drupal_array_set_nested_value($form, $dependent_location, $dependent_form_field);

    // Map the states based on the conjunctions.
    $states_new = array();
    foreach ($states as $state_key => $value) {

      // As the main object is ANDed together we can add the AND items directly.
      if (!empty($states[$state_key]['AND'])) {
        $states_new[$state_key] = $states[$state_key]['AND'];
      }

      // The OR and XOR groups are moved into a sub-array that has numeric keys
      // so that we get a JSON array and not an object, as required by the States
      // API for OR and XOR groupings.
      if (!empty($states[$state_key]['OR'])) {
        $or = array();
        foreach ($states[$state_key]['OR'] as $constraint_key => $constraint_value) {
          $or[] = array(
            $constraint_key => $constraint_value,
          );
        }

        // '1' as a string so that we get an object (which means logic groups
        // are ANDed together).
        $states_new[$state_key]['1'] = $or;
      }
      if (!empty($states[$state_key]['XOR'])) {
        $xor = array(
          'xor',
        );
        foreach ($states[$state_key]['XOR'] as $constraint_key => $constraint_value) {
          $xor[] = array(
            $constraint_key => $constraint_value,
          );
        }

        // '2' as a string so that we get an object.
        $states_new[$state_key]['2'] = $xor;
      }
    }
    $states = $states_new;

    // Add the #states property to the dependent field.
    drupal_array_set_nested_value($form, array_merge($dependent_location, array(
      '#states',
    )), $states);
    $has_states = TRUE;
  }
  if (empty($has_states)) {
    return $form;
  }
  $form['#attached']['library'][] = array(
    'conditional_fields',
    'conditional_fields',
  );

  // Add effect settings to the form.
  if ($effects) {
    $form['#attached']['js'][] = array(
      'data' => array(
        'conditionalFields' => array(
          'effects' => $effects,
        ),
      ),
      'type' => 'setting',
    );
  }

  // Validation callback to manage dependent fields validation.
  $form['#validate'][] = 'conditional_fields_form_validate';

  // Initialize validation information every time the form is rendered to avoid
  // stale data after a failed submission.
  $form_state['conditional_fields_untriggered_dependents'] = array();
  return $form;
}