/sites/all/modules/contrib/conditional_fields/conditional_fields.module
Unknown | 1993 lines | 1760 code | 233 blank | 0 comment | 0 complexity | aa76a8d242b83c6a6dc0deb1b9ed14e4 MD5 | raw file
Possible License(s): GPL-2.0, AGPL-1.0, LGPL-2.1
Large files files are truncated, but you can click here to view the full file
- <?php
- /**
- * @file
- * Define dependencies between fields based on their states and values.
- *
- * Conditional Fields for Drupal 7 is basically an user interface for the States
- * API, plus the ability to hide fields on certain conditions when viewing
- * content.
- */
- /**
- * Dependency is triggered if the dependee has a certain value.
- */
- define('CONDITIONAL_FIELDS_DEPENDENCY_VALUES_WIDGET', 1);
- /**
- * Dependency is triggered if the dependee has all values.
- */
- define('CONDITIONAL_FIELDS_DEPENDENCY_VALUES_AND', 2);
- /**
- * Dependency is triggered if the dependee has any of the values.
- */
- define('CONDITIONAL_FIELDS_DEPENDENCY_VALUES_OR', 3);
- /**
- * Dependency is triggered if the dependee has only one of the values.
- */
- define('CONDITIONAL_FIELDS_DEPENDENCY_VALUES_XOR', 4);
- /**
- * Dependency is triggered if the dependee does not have any of the values.
- */
- define('CONDITIONAL_FIELDS_DEPENDENCY_VALUES_NOT', 5);
- /**
- * Dependency is triggered if the dependee values match a regular expression.
- */
- define('CONDITIONAL_FIELDS_DEPENDENCY_VALUES_REGEX', 6);
- /**
- * Field view setting. Dependent is shown only if the dependency is triggered.
- */
- define('CONDITIONAL_FIELDS_FIELD_VIEW_EVALUATE', 1);
- /**
- * Field view setting. Dependent is shown only if the dependee is shown as well.
- */
- define('CONDITIONAL_FIELDS_FIELD_VIEW_HIDE_ORPHAN', 2);
- /**
- * Field view setting. Dependent is highlighted if the dependency is not
- * triggered.
- */
- define('CONDITIONAL_FIELDS_FIELD_VIEW_HIGHLIGHT', 3);
- /**
- * Field view setting. Dependent has a textual description of the dependency.
- */
- define('CONDITIONAL_FIELDS_FIELD_VIEW_DESCRIBE', 4);
- /**
- * Field view setting. Dependent is shown only if the dependee is shown as well
- * and the dependency evaluates to TRUE.
- */
- define('CONDITIONAL_FIELDS_FIELD_VIEW_HIDE_UNTRIGGERED_ORPHAN', 5);
- /**
- * Field edit setting. Dependent is shown only if the dependee is shown as well.
- */
- define('CONDITIONAL_FIELDS_FIELD_EDIT_HIDE_ORPHAN', 1);
- /**
- * Field edit setting. Dependent is shown only if the dependee is shown as well
- * and the dependency evaluates to TRUE.
- */
- define('CONDITIONAL_FIELDS_FIELD_EDIT_HIDE_UNTRIGGERED_ORPHAN', 2);
- /**
- * Field edit setting. Dependent is reset to its default values if the
- * dependency was not triggered when the form is submitted.
- */
- define('CONDITIONAL_FIELDS_FIELD_EDIT_RESET_UNTRIGGERED', 3);
- /**
- * Implements hook_permission().
- */
- function conditional_fields_permission() {
- return array(
- 'administer dependencies' => array(
- 'title' => t('Administer dependencies'),
- 'description' => t('View, edit and delete field dependencies.'),
- ),
- );
- }
- /**
- * Implements hook_menu().
- */
- function conditional_fields_menu() {
- $items = array();
- // Ensure the following is not executed until field_bundles is working and
- // tables are updated. Needed to avoid errors on initial installation.
- if (defined('MAINTENANCE_MODE')) {
- return $items;
- }
- $items['admin/structure/dependencies'] = array(
- 'title' => 'Field dependencies',
- 'description' => 'Administer field dependencies for the site.',
- 'page callback' => 'conditional_fields_dependencies_overview_page',
- 'access arguments' => array('administer dependencies'),
- 'file' => 'includes/conditional_fields.admin.inc',
- );
- $items['admin/structure/dependencies/overview'] = array(
- 'title' => 'Overview',
- 'type' => MENU_DEFAULT_LOCAL_TASK,
- 'weight' => 1,
- );
- $items['admin/structure/dependencies/edit/%conditional_fields_dependency'] = array(
- 'title' => 'Edit dependency',
- 'page callback' => 'drupal_get_form',
- 'page arguments' => array('conditional_fields_dependency_edit_form', 4),
- 'access arguments' => array('administer dependencies'),
- 'file' => 'includes/conditional_fields.admin.inc',
- );
- $items['admin/structure/dependencies/delete/%conditional_fields_dependency'] = array(
- 'title' => 'Delete dependency',
- 'page callback' => 'drupal_get_form',
- 'page arguments' => array('conditional_fields_dependency_delete_form', 4),
- 'access arguments' => array('administer dependencies'),
- 'file' => 'includes/conditional_fields.admin.inc',
- );
- // Some of the following code is copied from field_ui_menu().
- // Create tabs for all possible bundles.
- foreach (entity_get_info() as $entity_type => $entity_info) {
- if ($entity_info['fieldable']) {
- $items["admin/structure/dependencies/$entity_type"] = array(
- 'title' => $entity_info['label'],
- 'page arguments' => array(NULL, 3),
- 'access arguments' => array('administer dependencies'),
- 'type' => MENU_LOCAL_TASK,
- 'weight' => 2,
- );
- foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) {
- if (module_exists('field_ui') && isset($bundle_info['admin'])) {
- // Extract path information from the bundle and replace any "magic"
- // wildcard with a normal one.
- $path = preg_replace('/(%[a-z0-9_]*)/', '%', $bundle_info['admin']['path']);
- if (isset($bundle_info['admin']['bundle argument'])) {
- $bundle_pos = $bundle_info['admin']['bundle argument'];
- }
- else {
- $bundle_pos = $bundle_name;
- }
- $items["$path/dependencies"] = array(
- 'title' => $entity_type == 'comment' ? 'Comment dependencies' : 'Manage dependencies',
- 'page callback' => 'conditional_fields_dependencies_overview_page',
- 'page arguments' => array($bundle_pos, $entity_type),
- 'type' => MENU_LOCAL_TASK,
- 'weight' => $entity_type == 'comment' ? 4 : 2,
- 'file' => 'includes/conditional_fields.admin.inc',
- 'access arguments' => array('administer dependencies'),
- );
- }
- }
- }
- }
- return $items;
- }
- /**
- * Implements hook_forms().
- *
- * Maps all dependency add forms to the same callback.
- */
- function conditional_fields_forms() {
- foreach (entity_get_info() as $entity_type => $entity_info) {
- if ($entity_info['fieldable']) {
- foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) {
- $forms['conditional_fields_dependency_add_form_' . $entity_type . '_' . $bundle_name] = array(
- 'callback' => 'conditional_fields_dependency_add_form',
- 'callback arguments' => array($entity_type, $bundle_name),
- );
- }
- }
- }
- return $forms;
- }
- /**
- * Implements hook_js_alter().
- *
- * Overrides core states API with a patched version that allows multiple
- * conditions and OR/XOR logic.
- */
- function conditional_fields_js_alter(&$javascript) {
- // Since Drupal 7.14, states.js includes the OR/XOR patch.
- if (isset($javascript['misc/states.js']) && version_compare(VERSION, '7.14', '<')) {
- $javascript['misc/states.js']['data'] = drupal_get_path('module', 'conditional_fields') . '/js/states.js';
- }
- }
- /**
- * Implements hook_element_info_alter().
- * Adds an #after_build function to all form elements.
- */
- function conditional_fields_element_info_alter(&$types) {
- foreach ($types as $type => $info) {
- $types[$type]['#after_build'][] = 'conditional_fields_element_after_build';
- }
- }
- /**
- * Processes form elements with dependencies.
- *
- * Just adds a #conditional_fields property to the form with the needed
- * data, which is used later in conditional_fields_form_after_build():
- * - The fields #parents property.
- * - Field dependencies data.
- */
- function conditional_fields_element_after_build($element, &$form_state) {
- // Ensure that the element is a field.
- if (isset($element['#field_name'])) {
- $field = $element;
- }
- elseif (isset($element['#language'], $element[$element['#language']], $element[$element['#language']]['#field_name'])) {
- // Some fields are wrapped in containers before processing.
- $field = $element[$element['#language']];
- }
- else {
- return $element;
- }
- $form = &$form_state['complete form'];
- // Avoid processing fields in fields_ui administration pages.
- if (drupal_substr($form['#form_id'], 0, 9) == 'field_ui_') {
- return $element;
- }
- // Some fields do not have entity type and bundle properties. In this case we
- // try to use the properties from the form. This is not an optimal solution,
- // since in case of fields in entities within entities they might not correspond,
- // and their dependencies will not be loaded.
- if (isset($field['#entity_type'], $field['#bundle'])) {
- $entity_type = $field['#entity_type'];
- $bundle = $field['#bundle'];
- }
- elseif (isset($form['#entity_type'], $form['#bundle'])) {
- $entity_type = $form['#entity_type'];
- $bundle = $form['#bundle'];
- }
- else {
- return $element;
- }
- $dependencies = conditional_fields_load_dependencies($entity_type, $bundle);
- if (!$dependencies) {
- return $element;
- }
- // Attach dependent.
- if (isset($dependencies['dependents'][$field['#field_name']])) {
- foreach ($dependencies['dependents'][$field['#field_name']] as $id => $dependency) {
- if (!isset($form['#conditional_fields'][$field['#field_name']]['dependees'][$id])) {
- conditional_fields_attach_dependency($form, array('#field_name' => $dependency['dependee']), $field, $dependency['options'], $id);
- }
- }
- }
- // Attach dependee.
- // TODO: collect information about every element of the dependee widget, not
- // just the first encountered. This bottom-up approach would allow us to
- // define per-element sets of dependency values.
- if (isset($dependencies['dependees'][$field['#field_name']])) {
- foreach ($dependencies['dependees'][$field['#field_name']] as $id => $dependency) {
- if (!isset($form['#conditional_fields'][$field['#field_name']]['dependents'][$id])) {
- conditional_fields_attach_dependency($form, $field, array('#field_name' => $dependency['dependent']), $dependency['options'], $id);
- }
- }
- }
- return $element;
- }
- /**
- * Attaches a single dependency to a form.
- *
- * Call this function when defining or altering a form to create dependencies
- * dynamically.
- *
- * @param $form
- * The form where the dependency is attached.
- * @param $dependee
- * The dependee field form element. Either a string identifying the element
- * key in the form, or a fully built field array. Actually used properties of
- * the array are #field_name and #parents.
- * @param $dependent
- * The dependent field form element. Either a string identifying the element
- * key in the form, or a fully built field array. Actually used properties of
- * the array are #field_name and #field_parents.
- * @param $options
- * An array of dependency options with the following key/value pairs:
- * - state: The state applied to the dependent when the dependency is
- * triggered. See conditional_fields_states() for available states.
- * - condition: The condition for the dependency to be triggered. See
- * conditional_fields_conditions() for available conditions.
- * - values_set: One of the following constants:
- * - CONDITIONAL_FIELDS_DEPENDENCY_VALUES_WIDGET: Dependency is
- * triggered if the dependee has a certain value defined in 'value'.
- * - CONDITIONAL_FIELDS_DEPENDENCY_VALUES_AND: Dependency is triggered if
- * the dependee has all the values defined in 'values'.
- * - CONDITIONAL_FIELDS_DEPENDENCY_VALUES_OR: Dependency is triggered if the
- * dependee has any of the values defined in 'values'.
- * - CONDITIONAL_FIELDS_DEPENDENCY_VALUES_XOR: Dependency is triggered if
- * the dependee has only one of the values defined in 'values'.
- * - CONDITIONAL_FIELDS_DEPENDENCY_VALUES_NOT: Dependency is triggered if
- * the dependee does not have any of the values defined in 'values'.
- * - value: The value to be tested when 'values_set' is
- * CONDITIONAL_FIELDS_DEPENDENCY_VALUES_WIDGET. An associative array with
- * the same structure of the dependee field values as found in
- * $form_states['values] when the form is submitted. You can use
- * field_default_extract_form_values() to extract this array.
- * - values: The array of values to be tested when 'values_set' is not
- * CONDITIONAL_FIELDS_DEPENDENCY_VALUES_WIDGET.
- * - value_form: An associative array with the same structure of the dependee
- * field values as found in $form_state['input']['value']['field'] when the
- * form is submitted.
- * - effect: The jQuery effect associated to the state change. See
- * conditional_fields_effects() for available effects and options.
- * - effect_options: The options for the active effect.
- * - element_view: An associative array of field view behaviors with
- * CONDITIONAL_FIELDS_FIELD_VIEW_* constants as keys and the same constants
- * as values for enabled behaviors and 0 for disabled behaviors.
- * See conditional_fields_behaviors() for descriptions.
- * - element_view_per_role: Set to 1 to activate field view settings per role.
- * - element_view_roles: An associative array of field view settings per role
- * where the keys are role ids and the values are arrays with the same
- * structure of 'element_view'.
- * - element_edit: An associative array of field edit behaviors with
- * CONDITIONAL_FIELDS_FIELD_EDIT_* constants as keys and the same constants
- * as values for enabled behaviors and 0 for disabled behaviors.
- * See conditional_fields_behaviors() for descriptions.
- * - element_edit_per_role: Set to 1 to activate field edit settings per role.
- * - element_edit_roles: An associative array of field edit settings per role
- * where the keys are role ids and the values are arrays with the same
- * structure of 'element_edit'.
- * - selector: (optional) Custom jQuery selector for the dependee.
- * @param $id
- * (internal use) The identifier for the dependency. Omit this parameter when
- * attaching a custom dependency.
- *
- * Note that you don't need to manually set all these options, since default
- * settings are always provided.
- */
- function conditional_fields_attach_dependency(&$form, $dependee, $dependent, $options, $id = 0) {
- $options += conditional_fields_dependency_default_options();
- // The absence of the $id parameter identifies a custom dependency.
- if (!$id) {
- // String values are accepted to simplify usage of this function with custom
- // forms.
- if (is_string($dependee) && is_string($dependent)) {
- $dependee = array(
- '#field_name' => $dependee,
- '#parents' => array($dependee),
- );
- $dependent = array(
- '#field_name' => $dependent,
- '#field_parents' => array($dependent),
- );
- // Custom dependencies have automatically assigned a progressive id.
- static $current_id;
- if (!$current_id) {
- $current_id = 1;
- }
- $id = $current_id;
- $current_id++;
- }
- }
- // Attach dependee.
- // Use the #parents property of the dependee instead of #field_parents since
- // we will need access to the full structure of the widget.
- if (isset($dependee['#parents'])) {
- $form['#conditional_fields'][$dependee['#field_name']]['parents'] = $dependee['#parents'];
- $form['#conditional_fields'][$dependee['#field_name']]['dependents'][$id] = array(
- 'dependent' => $dependent['#field_name'],
- 'options' => $options,
- );
- }
- // Attach dependent.
- if (isset($dependent['#field_parents'])) {
- $dependent_parents = $dependent['#field_parents'];
- }
- elseif (isset($dependent['#parents'])) {
- $dependent_parents = $dependent['#parents'];
- }
- if (isset($dependent_parents)) {
- $form['#conditional_fields'][$dependent['#field_name']]['field_parents'] = $dependent_parents;
- $form['#conditional_fields'][$dependent['#field_name']]['dependees'][$id] = array(
- 'dependee' => $dependee['#field_name'],
- 'options' => $options,
- );
- }
- // Actual processing is done in conditional_fields_form_after_build().
- // Append the property so the callback runs last.
- _conditional_fields_element_add_property($form, '#after_build', 'conditional_fields_form_after_build', 'append');
- }
- /**
- * 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.
- */
- 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]);
- 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.
- _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) {
- if (empty($states[$key][$options['grouping']])) {
- $states[$key][$options['grouping']] = $constraints;
- }
- else {
- $states[$key][$options['grouping']] = array_merge($states[$key][$options['grouping']], $constraints);
- }
- }
- // 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.
- if (!empty($behaviors)) {
- foreach ($behaviors as $behavior) {
- // Custom behaviors are callbacks.
- if (function_exists($$behavior)) {
- $$behavior('edit', $form, $form_state, $dependent, $dependencies);
- }
- }
- }
- 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']['js'][] = drupal_get_path('module', 'conditional_fields') . '/js/conditional_fields.js';
- // 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;
- }
- /**
- * Dependent field validation callback.
- *
- * If the dependencies of a dependent field are not triggered, the validation
- * errors that it might have thrown must be removed, together with its submitted
- * values. This will simulate the field not being present in the form at all.
- * In this field-level callback we just collect needed information and store it
- * in $form_state. Values and errors will be removed in a single sweep in
- * conditional_fields_form_validate(), which runs at the end of the validation
- * cycle.
- *
- * @see conditional_fields_form_validate()
- */
- function conditional_fields_dependent_validate($element, &$form_state, $form) {
- $dependent = $element[$element['#language']];
- // Check if this field's dependencies were triggered.
- if (conditional_fields_evaluate_dependencies($dependent, $form, $form_state)) {
- return;
- }
- // Mark submitted values for removal. We have to remove them after all fields
- // have been validated to avoid collision between dependencies.
- $form_state_addition['parents'] = $dependent['#array_parents'];
- // Optional behavior: reset the field to its default values.
- // Default values are always valid, so it's safe to skip validation.
- if (!empty($element['#conditional_fields_reset_if_untriggered'])) {
- $form_state_addition['reset'] = TRUE;
- }
- // Tag validation errors previously set on this field for removal in
- // conditional_fields_form_validate().
- $errors = form_get_errors();
- if ($errors) {
- $error_key = implode('][', $dependent['#parents']);
- foreach ($errors as $name => $error) {
- // An error triggered by this field might have been set on a descendant
- // element. This also means that so there can be multiple errors on the
- // same field (even though Drupal doesn't support multiple errors on the
- // same element).
- if (strpos($name, $error_key) === 0) {
- $field_errors[$name] = $error;
- }
- }
- }
- if (!empty($field_errors)) {
- $form_state_addition['errors'] = $field_errors;
- }
- $form_state['conditional_fields_untriggered_dependents'][] = $form_state_addition;
- }
- /**
- * Extracts submitted field values during form validation.
- *
- * @return
- * The requested field values parent. Actual field vales are stored under the
- * key $element['#field_name'].
- */
- function conditional_fields_form_field_get_values($element, $form_state) {
- // Fall back to #parents to support custom dependencies.
- $parents = isset($element['#field_parents']) ? $element['#field_parents'] : $element['#parents'];
- return drupal_array_get_nested_value($form_state['values'], $parents);
- }
- /**
- * Validation callback for any form with conditional fields.
- *
- * This validation callback is added to all forms that contain fields with
- * dependencies. It removes all validation errors from dependent fields whose
- * dependencies are not triggered, which were collected at field-level
- * validation in conditional_fields_dependent_validate().
- *
- * @see conditional_fields_dependent_validate()
- */
- function conditional_fields_form_validate($form, &$form_state) {
- if (empty($form_state['conditional_fields_untriggered_dependents'])) {
- return;
- }
- $untriggered_dependents_errors = array();
- foreach ($form_state['conditional_fields_untriggered_dependents'] as $field) {
- $dependent = drupal_array_get_nested_value($form, $field['parents']);
- $field_values_location = conditional_fields_form_field_get_values($dependent, $form_state);
- // If we couldn't find a location for the field's submitted values, let the
- // validation errors pass through to avoid security holes.
- if (!isset($field_values_location[$dependent['#field_name']])) {
- continue;
- }
- if (empty($field['reset'])) {
- unset($field_values_location[$dependent['#field_name']]);
- }
- else {
- $dependent_info = field_form_get_state($dependent['#field_parents'], $dependent['#field_name'], $dependent['#language'], $form_state);
- $field_values_location[$dependent['#field_name']][$dependent['#language']] = field_get_default_value($dependent_info['instance']['entity_type'], NULL, $dependent_info['field'], $dependent_info['instance'], $dependent['#language']);
- }
- // Save the changed array back in place.
- // Do not use form_set_value() since it assumes that the values are located at
- // $form_state['values'][ ... $element['#parents'] ... ], while the
- // documentation of hook_field_widget_form() states that field values are
- // $form_state['values'][ ... $element['#field_parents'] ... ].
- drupal_array_set_nested_value($form_state['values'], $dependent['#field_parents'], $field_values_location);
- if (!empty($field['errors'])) {
- $untriggered_dependents_errors = array_merge($untriggered_dependents_errors, $field['errors']);
- }
- }
- if (!empty($untriggered_dependents_errors)) {
- // Since Drupal provides no clean way to selectively remove error messages,
- // we have to store all current form errors and error messages, clear them,
- // filter out from our stored values the errors originating from untriggered
- // dependent fields, and then reinstate remaining errors and messages.
- $errors = array_diff_assoc((array) form_get_errors(), $untriggered_dependents_errors);
- form_clear_error();
- $error_messages = drupal_get_messages('error');
- $removed_messages = array_values($untriggered_dependents_errors);
- // Reinstate remaining errors.
- foreach ($errors as $name => $error) {
- form_set_error($name, $error);
- // form_set_error() calls drupal_set_message(), so we have to filter out
- // these from the messages to avoid duplicates.
- $removed_messages[] = $error;
- }
- // Reinstate remaining error messages (which, at this point, are messages that
- // were originated outside of the validation process).
- foreach (array_diff($error_messages['error'], $removed_messages) as $message) {
- drupal_set_message($message, 'error');
- }
- }
- }
- /**
- * Helper function to add a property/value pair to a render array safely without
- * overriding any pre-existing value.
- *
- * @param $position
- * 'append' if $value should be inserted at the end of the $element[$property]
- * array, any other value to insert it at the beginning.
- *
- */
- function _conditional_fields_element_add_property(&$element, $property, $value, $position = 'prepend') {
- // Avoid overriding default element properties that might not yet be set.
- if (!isset($element[$property])) {
- $element[$property] = isset($element['#type']) ? element_info_property($element['#type'], $property, array()) : array();
- }
- if (in_array($value, $element[$property])) {
- return;
- }
- switch ($position) {
- case 'append':
- $element[$property] = array_merge($element[$property], (array) $value);
- break;
- case 'prepend':
- default:
- $element[$property] = array_merge((array) $value, $element[$property]);
- break;
- }
- }
- /**
- * Implements hook_entity_view_alter().
- *
- * Applies entity view logic to conditional fields.
- */
- function conditional_fields_entity_view_alter(&$build, $type) {
- if (!(isset($build['#entity_type'], $build['#bundle']) && $dependencies = conditional_fields_load_dependencies($build['#entity_type'], $build['#bundle']))) {
- return;
- }
- $evaluated_dependents = array();
- foreach ($dependencies['dependents'] as $dependent => $dependency) {
- if (empty($build[$dependent]['#access'])) {
- continue;
- }
- foreach ($dependency as $dependency_options) {
- $dependee = $dependency_options['dependee'];
- $options = $dependency_options['options'];
- // We can interface with the States API only through the Value condition.
- if ($options['condition'] != 'value') {
- continue;
- }
- // Determine field view behaviors.
- // If this dependent has multiple dependencies, only the logic of the
- // first dependency will be taken into account.
- if (!isset($behaviors)) {
- $behaviors = conditional_fields_field_behaviors('view', $options);
- }
- // Manage orphan fields (dependents with no dependees).
- $evaluate = in_array(CONDITIONAL_FIELDS_FIELD_VIEW_EVALUATE, $behaviors);
- $hide_orphan = in_array(CONDITIONAL_FIELDS_FIELD_VIEW_HIDE_ORPHAN, $behaviors);
- $hide_untriggered_orphan = in_array(CONDITIONAL_FIELDS_FIELD_VIEW_HIDE_UNTRIGGERED_ORPHAN, $behaviors);
- $is_orphan = empty($build[$dependee]['#access']);
- if ($is_orphan) {
- // Hide the dependent. No need to evaluate the dependency.
- if ($hide_orphan) {
- $build[$dependent]['#access'] = FALSE;
- continue;
- }
- if ($hide_untriggered_orphan) {
- $evaluate = TRUE;
- }
- if ($evaluate) {
- // We have to look for the dependee in the entity object.
- // TODO: Is it possible to avoid hardcoding this?
- switch ($type) {
- case 'node':
- $entity_property = '#node';
- break;
- case 'user':
- $entity_property = '#account';
- break;
- case 'term':
- $entity_property = '#term';
- break;
- case 'field_collection_item':
- case 'profile2':
- default:
- $entity_property = '#entity';
- }
- // If we didn't find the field, there is nothing more we can do.
- if (!isset($build[$entity_property]->$dependee)) {
- continue;
- }
- $items = $build[$entity_property]->$dependee;
- // Values are keyed by language here, remove it.
- $items = array_shift($items);
- }
- }
- else {
- $items = $build[$dependee]['#items'];
- }
- if ($evaluate) {
- $evaluated_dependents[$dependent][$options['grouping']][] = conditional_fields_evaluate_dependency('view', $items, $options);
- }
- }
- if (isset($evaluated_dependents[$dependent])) {
- $is_triggered = conditional_fields_evaluate_grouping($evaluated_dependents[$dependent]);
- foreach ($behaviors as $behavior) {
- switch ($behavior) {
- case CONDITIONAL_FIELDS_FIELD_VIEW_EVALUATE:
- // Hide the dependent if it is not triggered.
- if (!$is_triggered) {
- $build[$dependent]['#access'] = FALSE;
- }
- break;
- case CONDITIONAL_FIELDS_FIELD_VIEW_HIDE_ORPHAN:
- // This case was handled while looking for the field.
- break;
- case CONDITIONAL_FIELDS_FIELD_VIEW_HIDE_UNTRIGGERED_ORPHAN:
- // Hide the dependent if the dependee is not viewable and the dependency is not triggered.
- if ($is_orphan && !$is_triggered) {
- $build[$dependent]['#access'] = FALSE;
- }
- break;
- case CONDITIONAL_FIELDS_FIELD_VIEW_HIGHLIGHT:
- // Show the dependent themed like an error message if it is not triggered.
- if (!$is_triggered) {
- $build[$dependent]['#prefix'] = isset($build[$dependent]['#prefix']) ? '<div class="messages error">' . $build[$dependent]['#prefix'] : '<div class="messages error">';
- $build[$dependent]['#suffix'] = isset($build[$dependent]['#suffix']) ? $build[$dependent]['#suffix'] . '</div>' : '</div>';
- }
- break;
- case CONDITIONAL_FIELDS_FIELD_VIEW_DESCRIBE:
- // Show a textual description of the dependency under the dependent field.
- if ($build[$dependent]['#access']) {
- $dependee_title = isset($build[$dependee]['#title']) ? $build[$dependee]['#title'] : $dependee;
- $dependent_title = isset($build[$dependent]['#title']) ? $build[$dependent]['#title'] : $dependent;
- $description = conditional_fields_dependency_description($dependee_title, $dependent_title, $options);
- if (isset($build[$dependent]['#suffix'])) {
- $description = $build[$dependent]['#suffix'] . $description;
- }
- $build[$dependent]['#suffix'] = $description;
- }
- break;
- default:
- // Custom behaviors are callbacks.
- $$behavior('view', $dependee, $dependent, $is_triggered, $dependencies, $build, $type);
- break;
- }
- if (empty($build[$dependent]['#access'])) {
- break;
- }
- }
- }
- unset($behaviors);
- }
- }
- /**
- * Evaluates an array with 'AND', 'OR' and 'XOR' groupings,
- * each containing a list of boolean values.
- */
- function conditional_fields_evaluate_grouping($groups) {
- $or = $and = $xor = TRUE;
- if (!empty($groups['OR'])) {
- $or = in_array(TRUE, $groups['OR']);
- }
- if (!empty($groups['AND'])) {
- $and = !in_array(FALSE, $groups['AND']);
- }
- if (!empty($groups['XOR'])) {
- $xor = array_sum($groups['XOR']) == 1;
- }
- return $or && $and && $xor;
- }
- /**
- * Evaluate a set of dependencies for a dependent field.
- *
- * @param $dependent
- * The field form element in the current language.
- */
- function conditional_fields_evaluate_dependencies($dependent, $form, $form_state) {
- $dependencies = $form['#conditional_fields'][$dependent['#field_name']]['dependees'];
- $evaluated_dependees = array();
- foreach ($dependencies as $dependency_id => $dependency) {
- // Extract field values from submitted values.
- $dependee = $dependency['dependee'];
- $dependee_parents = $form['#conditional_fields'][$dependee]['parents'];
- // We have the parents of the field, but depending on the entity type and
- // the widget type, they may include additional elements that are actually
- // part of the value. So we find the depth of the field inside the form
- // structure and use the parents only up to that depth.
- $dependee_parents_keys = array_flip($dependee_parents);
- $dependee_parent = drupal_array_get_nested_value($form, array_slice($dependee_parents, 0, $dependee_parents_keys[$dependee]));
- $values = conditional_fields_form_field_get_values($dependee_parent[$dependee], $form_state);
- // Remove the language key.
- if (isset($dependee_parent[$dependee]['#language'], $values[$dependee_parent[$dependee]['#language']])) {
- $values = $values[$dependee_parent[$dependee]['#language']];
- }
- $evaluated_dependees[$dependent['#field_name']][$dependency['options']['grouping']][] = conditional_fields_evaluate_dependency('edit', $values, $dependency['options']);
- }
- return conditional_fields_evaluate_grouping($evaluated_dependees[$dependent['#field_name']]);
- }
- /**
- * Evaluate if a dependency meets the requirements to be triggered.
- *
- * @param $context
- * 'edit' if $values are extracted from $form_state or 'view' if
- * $values are extracted from an entity.
- */
- function conditional_fields_evaluate_dependency($context, $values, $options) {
- if ($options['values_set'] == CONDITIONAL_FIELDS_DEPENDENCY_VALUES_WIDGET) {
- $dependency_values = $context == 'view' ? $options['value'] : $options['value_form'];
- // Simple case: both values are strings or integers. Should never happen in
- // view context, but does no harm to check anyway.
- if (!is_array($values)) {
- // Options elements consider "_none" value same as empty.
- $values = $values === '_none' ? '' : $values;
- if (!is_array($dependency_values)) {
- // Some widgets store integers, but values saved in $dependency_values
- // are always strings. Convert them to integers because we want to do a
- // strict equality check to differentiate empty strings from '0'.
- if (is_int($values) && is_numeric($dependency_values)) {
- $dependency_values = (int) $dependency_values;
- }
- return $dependency_values === $values;
- }
- // If $values is a string and $dependency_values an array, convert $values
- // to the standard field array form format. This covers cases like single
- // value textfields.
- $values = array(array('value' => $values));
- }
- // If we are in form context, we are almost done.
- if ($context == 'edit') {
- // If $dependency_values is not an array, we can only assume that it
- // should map to the first key of the first value of $values.
- if (!is_array($dependency_values)) {
- $key = current(array_keys((array) current($values)));
- $dependency_values = array(array($key => $dependency_values));
- }
- // Compare arrays recursively ignoring keys, since multiple select widgets
- // values have numeric keys in form format and string keys in storage
- // format.
- return array_values($dependency_values) == array_values($values);
- }
- // $values, when viewing fields, may contain all sort of additional
- // information, so filter out from $values the keys that are not present in
- // $dependency_values.
- // Values here are alway keyed by delta (regardless of multiple value
- // settings).
- foreach ($values as $delta => &$value) {
- if (isset($dependency_values[$delta])) {
- $value = array_intersect_key($value, $dependency_values[$delta]);
- foreach ($value as $key => &$element_value) {
- if (isset($dependency_values[$delta][$key]) && is_int($dependency_values[$delta][$key]) && is_numeric($element_value)) {
- $element_value = (int) $element_value;
- }
- }
- }
- }
- // Compare values.
- foreach ($dependency_values as $delta => $dependency_value) {
- if (!isset($values[$delta])) {
- return FALSE;
- }
- foreach ($dependency_value as $key => $dependency_element_value) {
- // Ignore keys set in the field and not in the dependency.
- if (isset($values[$delta][$key]) && $values[$delta][$key] !== $dependency_element_value) {
- return FALSE;
- }
- }
- }
- return TRUE;
- }
- // Flatten array of values.
- $reference_values = array();
- foreach ((array) $values as $value) {
- // TODO: support multiple values.
- $reference_values[] = is_array($value) ? array_shift($value) : $value;
- }
- // Regular expression method.
- if ($options['values_set'] == CONDITIONAL_FIELDS_DEPENDENCY_VALUES_REGEX) {
- foreach ($reference_values as $reference_value) {
- if (!preg_match('/' . $options['value']['RegExp'] . '/', $reference_value)) {
- return FALSE;
- }
- }
- return TRUE;
- }
- switch ($options['values_set']) {
- case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_AND:
- $diff = array_diff($options['values'], $reference_values);
- return empty($diff);
- case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_OR:
- $intersect = array_intersect($options['values'], $reference_values);
- return !empty($intersect);
- case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_XOR:
- $intersect = array_intersect($options['values'], $reference_values);
- return count($intersect) == 1;
- case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_NOT:
- $intersect = array_intersect($options['values'], $reference_values);
- return empty($intersect);
- }
- }
- /**
- * Determine which dependency behaviors should be used in forms and content
- * display, depending on dependency options and user roles.
- *
- * @param $op
- * 'view' or 'edit'.
- * @param $options
- * Dependency options.
- *
- * @return
- * An array of behaviors.
- *
- */
- function conditional_fields_field_behaviors($op, $options) {
- global $user;
- if ($options['element_' . $op . '_per_role']) {
- foreach ($options['element_' . $op . '_roles'] as $rid => $role_behaviors) {
- if (isset($user->roles[$rid])) {
- $behaviors = $role_behaviors;
- break;
- }
- }
- }
- else {
- $behaviors = $options['element_' . $op];
- }
- // Filter out inactive behaviors.
- $behaviors = array_filter($behaviors);
- return $behaviors;
- }
- /**
- * Builds a jQuery selector from the name attribute of a field.
- * TODO: support custom selectors with %lang and %key placeholders.
- */
- function conditional_fields_field_selector($field) {
- if (isset($field['#attributes']['name'])) {
- return '[name="' . $field['#attributes']['name'] . '"]';
- }
- if (isset($field['#name'])) {
- return '[name="' . $field['#name'] . '"]';
- }
- // Try with id if name is not found.
- if (isset($field['#attributes']['id'])) {
- return '#' . $field['#attributes']['id'];
- }
- if (isset($field['#id'])) {
- return '#' . $field['#id'];
- }
- return FALSE;
- }
- /**
- * Provides default options for a dependency.
- *
- * For an explanation of available options,
- * @see conditional_fields_field_attach_dependency()
- */
- function conditional_fields_dependency_default_options() {
- return array(
- 'state' => 'visible',
- 'condition' => 'value',
- 'grouping' => 'AND',
- 'values_set' => CONDITIONAL_FIELDS_DEPENDENCY_VALUES_WIDGET,
- 'value' => array(),
- 'values' => array(),
- 'value_form' => array(),
- 'effect' => 'show',
- 'effect_options' => array(),
- 'element_view' => array(
- CONDITIONAL_FIELDS_FIELD_VIEW_EVALUATE => CONDITIONAL_FIELDS_FIELD_VIEW_EVALUATE,
- CONDITIONAL_FIELDS_FIELD_VIEW_HIDE_ORPHAN => CONDITIONAL_FIELDS_FIELD_VIEW_HIDE_ORPHAN,
- CONDITIONAL_FIELDS_FIELD_VIEW_HIDE_UNTRIGGERED_ORPHAN => 0,
- CONDITIONAL_FIELDS_FIELD_VIEW_HIGHLIGHT => 0,
- CONDITIONAL_FIELDS_FIELD_VIEW_DESCRIBE => 0,
- ),
- 'element_view_per_role' => 0,
- 'element_view_roles' => array(),
- 'element_edit' => array(
- CONDITIONAL_FIELDS_FIELD_EDIT_HIDE_ORPHAN => CONDITIONAL_FIELDS_FIELD_EDIT_HIDE_ORPHAN,
- CONDITIONAL_FIELDS_FIELD_EDIT_HIDE_UNTRIGGERED_ORPHAN => 0,
- CONDITIONAL_FIELDS_FIELD_EDIT_RESET_UNTRIGGERED => 0,
- ),
- 'element_edit_per_role' => 0,
- 'element_edit_roles' => array(),
- 'selector' => '',
- );
- }
- /**
- * Loads all dependencies from the database.
- *
- * The result can be filtered by providing an entity type and a bundle name.
- */
- function conditional_fields_load_dependencies($entity_type = NULL, $bundle = NULL) {
- // Use the advanced drupal_static() pattern.
- static $dependencies;
- if (!isset($dependencies)) {
- $dependencies = &drupal_static(__FUNCTION__);
- }
- if (!$dependencies) {
- $dependencies = array();
- }
- if (!isset($dependencies[$entity_type][$bundle])) {
- if (!empty($entity_type) && !empty($bundle)) {
- $dependencies[$entity_type][$bundle] = array();
- }
- $default_options = conditional_fields_dependency_default_options();
- $select = db_select('conditional_fields', 'cf')
- ->fields('cf', array('id', 'options'))
- ->orderBy('cf.dependent');
- $fci_depende = $select->join('field_config_instance', 'fci_dependee', 'cf.dependee = fci_dependee.id');
- $fci_dependent = $select->join('field_config_instance', 'fci_dependent', 'cf.dependent = fci_dependent.id');
- $select->addField($fci_depende, 'field_name', 'dependee');
- $select->addField($fci_dependent, 'field_name', 'dependent');
- $select->addField($fci_depende, 'entity_type');
- $select->addField($fci_depende, 'bundle');
- if ($entity_type) {
- $select->condition(
- db_and()
- ->condition('fci_dependee.entity_type', $entity_type)
- ->condition('fci_dependent.entity_type', $entity_type)
- );
- }
- if ($bundle) {
- $select->condition(
- db_and()
- ->condition('fci_dependee.bundle', $bundle)
- ->condition('fci_dependent.bundle', $bundle)
- );
- }
- $result = $select->execute();
- foreach ($result as $dependency) {
- $result_entity_type = $entity_type ? $entity_type : $dependency->entity_type;
- $result_bundle = $bundle ? $bundle : $dependency->bundle;
- $options = unserialize($dependency->options);
- $options += $default_options…
Large files files are truncated, but you can click here to view the full file