Guest User

conditional_fields.module - 7.x-3.0-alpha2+10-dev and #341

a guest
Jan 30th, 2018
405
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 77.48 KB | None | 0 0
  1. <?php
  2.  
  3. /**
  4.  * @file
  5.  * Define dependencies between fields based on their states and values.
  6.  *
  7.  * Conditional Fields for Drupal 7 is basically an user interface for the States
  8.  * API, plus the ability to hide fields on certain conditions when viewing
  9.  * content.
  10.  */
  11.  
  12. /**
  13.  * Dependency is triggered if the dependee has a certain value.
  14.  */
  15. define('CONDITIONAL_FIELDS_DEPENDENCY_VALUES_WIDGET', 1);
  16.  
  17. /**
  18.  * Dependency is triggered if the dependee has all values.
  19.  */
  20. define('CONDITIONAL_FIELDS_DEPENDENCY_VALUES_AND', 2);
  21.  
  22. /**
  23.  * Dependency is triggered if the dependee has any of the values.
  24.  */
  25. define('CONDITIONAL_FIELDS_DEPENDENCY_VALUES_OR', 3);
  26.  
  27. /**
  28.  * Dependency is triggered if the dependee has only one of the values.
  29.  */
  30. define('CONDITIONAL_FIELDS_DEPENDENCY_VALUES_XOR', 4);
  31.  
  32. /**
  33.  * Dependency is triggered if the dependee does not have any of the values.
  34.  */
  35. define('CONDITIONAL_FIELDS_DEPENDENCY_VALUES_NOT', 5);
  36.  
  37. /**
  38.  * Dependency is triggered if the dependee values match a regular expression.
  39.  */
  40. define('CONDITIONAL_FIELDS_DEPENDENCY_VALUES_REGEX', 6);
  41.  
  42. /**
  43.  * Field view setting. Dependent is shown only if the dependency is triggered.
  44.  */
  45. define('CONDITIONAL_FIELDS_FIELD_VIEW_EVALUATE', 1);
  46.  
  47. /**
  48.  * Field view setting. Dependent is shown only if the dependee is shown as well.
  49.  */
  50. define('CONDITIONAL_FIELDS_FIELD_VIEW_HIDE_ORPHAN', 2);
  51.  
  52. /**
  53.  * Field view setting. Dependent is highlighted if the dependency is not
  54.  * triggered.
  55.  */
  56. define('CONDITIONAL_FIELDS_FIELD_VIEW_HIGHLIGHT', 3);
  57.  
  58. /**
  59.  * Field view setting. Dependent has a textual description of the dependency.
  60.  */
  61. define('CONDITIONAL_FIELDS_FIELD_VIEW_DESCRIBE', 4);
  62.  
  63. /**
  64.  * Field view setting. Dependent is shown only if the dependee is shown as well
  65.  * and the dependency evaluates to TRUE.
  66.  */
  67. define('CONDITIONAL_FIELDS_FIELD_VIEW_HIDE_UNTRIGGERED_ORPHAN', 5);
  68.  
  69. /**
  70.  * Field edit setting. Dependent is shown only if the dependee is shown as well.
  71.  */
  72. define('CONDITIONAL_FIELDS_FIELD_EDIT_HIDE_ORPHAN', 1);
  73.  
  74. /**
  75.  * Field edit setting. Dependent is shown only if the dependee is shown as well
  76.  * and the dependency evaluates to TRUE.
  77.  */
  78. define('CONDITIONAL_FIELDS_FIELD_EDIT_HIDE_UNTRIGGERED_ORPHAN', 2);
  79.  
  80. /**
  81.  * Field edit setting. Dependent is reset to its default values if the
  82.  * dependency was not triggered when the form is submitted.
  83.  */
  84. define('CONDITIONAL_FIELDS_FIELD_EDIT_RESET_UNTRIGGERED', 3);
  85.  
  86. /**
  87.  * Implements hook_permission().
  88.  */
  89. function conditional_fields_permission() {
  90.   return array(
  91.     'administer dependencies' => array(
  92.       'title' => t('Administer dependencies'),
  93.       'description' => t('View, edit and delete field dependencies.'),
  94.     ),
  95.   );
  96. }
  97.  
  98. /**
  99.  * Implements hook_menu().
  100.  */
  101. function conditional_fields_menu() {
  102.   $items = array();
  103.  
  104.   // Ensure the following is not executed until field_bundles is working and
  105.   // tables are updated. Needed to avoid errors on initial installation.
  106.   if (defined('MAINTENANCE_MODE')) {
  107.     return $items;
  108.   }
  109.  
  110.   $items['admin/structure/dependencies'] = array(
  111.     'title' => 'Field dependencies',
  112.     'description' =>  'Administer field dependencies for the site.',
  113.     'page callback' => 'conditional_fields_dependencies_overview_page',
  114.     'access arguments' => array('administer dependencies'),
  115.     'file' => 'includes/conditional_fields.admin.inc',
  116.   );
  117.  
  118.   $items['admin/structure/dependencies/overview'] = array(
  119.     'title' => 'Overview',
  120.     'type' => MENU_DEFAULT_LOCAL_TASK,
  121.     'weight' => 1,
  122.   );
  123.  
  124.   $items['admin/structure/dependencies/edit/%conditional_fields_dependency'] = array(
  125.     'title' => 'Edit dependency',
  126.     'page callback' => 'drupal_get_form',
  127.     'page arguments' => array('conditional_fields_dependency_edit_form', 4),
  128.     'access arguments' => array('administer dependencies'),
  129.     'file' => 'includes/conditional_fields.admin.inc',
  130.   );
  131.  
  132.   $items['admin/structure/dependencies/delete/%conditional_fields_dependency'] = array(
  133.     'title' => 'Delete dependency',
  134.     'page callback' => 'drupal_get_form',
  135.     'page arguments' => array('conditional_fields_dependency_delete_form', 4),
  136.     'access arguments' => array('administer dependencies'),
  137.     'file' => 'includes/conditional_fields.admin.inc',
  138.   );
  139.  
  140.   // Some of the following code is copied from field_ui_menu().
  141.  
  142.   // Create tabs for all possible bundles.
  143.   foreach (entity_get_info() as $entity_type => $entity_info) {
  144.     if ($entity_info['fieldable']) {
  145.       $items["admin/structure/dependencies/$entity_type"] = array(
  146.         'title' => $entity_info['label'],
  147.         'page arguments' => array(NULL, 3),
  148.         'access arguments' => array('administer dependencies'),
  149.         'type' => MENU_LOCAL_TASK,
  150.         'weight' => 2,
  151.       );
  152.  
  153.       foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) {
  154.         if (module_exists('field_ui') && isset($bundle_info['admin'])) {
  155.           // Extract path information from the bundle and replace any "magic"
  156.           // wildcard with a normal one.
  157.           $path = preg_replace('/(%[a-z0-9_]*)/', '%', $bundle_info['admin']['path']);
  158.  
  159.           if (isset($bundle_info['admin']['bundle argument'])) {
  160.             $bundle_pos = $bundle_info['admin']['bundle argument'];
  161.           }
  162.           else {
  163.             $bundle_pos = $bundle_name;
  164.           }
  165.  
  166.           $items["$path/dependencies"] = array(
  167.             'title' => $entity_type == 'comment' ? 'Comment dependencies' : 'Manage dependencies',
  168.             'page callback' => 'conditional_fields_dependencies_overview_page',
  169.             'page arguments' => array($bundle_pos, $entity_type),
  170.             'type' => MENU_LOCAL_TASK,
  171.             'weight' => $entity_type == 'comment' ? 4 : 2,
  172.             'file' => 'includes/conditional_fields.admin.inc',
  173.             'access arguments' => array('administer dependencies'),
  174.           );
  175.         }
  176.       }
  177.     }
  178.   }
  179.  
  180.   return $items;
  181. }
  182.  
  183. /**
  184.  * Implements hook_forms().
  185.  *
  186.  * Maps all dependency add forms to the same callback.
  187.  */
  188. function conditional_fields_forms() {
  189.   foreach (entity_get_info() as $entity_type => $entity_info) {
  190.     if ($entity_info['fieldable']) {
  191.       foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) {
  192.         $forms['conditional_fields_dependency_add_form_' . $entity_type . '_' . $bundle_name] = array(
  193.           'callback' => 'conditional_fields_dependency_add_form',
  194.           'callback arguments' => array($entity_type, $bundle_name),
  195.         );
  196.       }
  197.     }
  198.   }
  199.  
  200.   return $forms;
  201. }
  202.  
  203. /**
  204.  * Implements hook_element_info_alter().
  205.  * Adds an #after_build function to all form elements.
  206.  */
  207. function conditional_fields_element_info_alter(&$types) {
  208.   foreach ($types as $type => $info) {
  209.     $types[$type]['#after_build'][] = 'conditional_fields_element_after_build';
  210.   }
  211. }
  212.  
  213. /**
  214.  * Processes form elements with dependencies.
  215.  *
  216.  * Just adds a #conditional_fields property to the form with the needed
  217.  * data, which is used later in conditional_fields_form_after_build():
  218.  * - The fields #parents property.
  219.  * - Field dependencies data.
  220.  */
  221. function conditional_fields_element_after_build($element, &$form_state) {
  222.   // Some fields are wrapped in containers before processing. Wrapped data must
  223.   // take precedence over the container, because Entity Translation and
  224.   // possibly other modules add #field_name to the container as well.
  225.   if (isset($element['#language'], $element[$element['#language']], $element[$element['#language']]['#field_name'])) {
  226.     $field = $element[$element['#language']];
  227.   }
  228.   elseif (isset($element['#field_name'])) {
  229.     $field = $element;
  230.   }
  231.   else {
  232.     return $element;
  233.   }
  234.  
  235.   $form = &$form_state['complete form'];
  236.  
  237.   // Avoid processing fields in fields_ui administration pages.
  238.   if (drupal_substr($form['#form_id'], 0, 9) == 'field_ui_') {
  239.     return $element;
  240.   }
  241.  
  242.   // Some fields do not have entity type and bundle properties. In this case we
  243.   // try to use the properties from the form. This is not an optimal solution,
  244.   // since in case of fields in entities within entities they might not correspond,
  245.   // and their dependencies will not be loaded.
  246.   if (isset($field['#entity_type'], $field['#bundle'])) {
  247.     $entity_type = $field['#entity_type'];
  248.     $bundle = $field['#bundle'];
  249.   }
  250.   elseif (isset($field[0]['#entity_type']) && $bundle = _conditional_fields_field_parent_bundle($field)) {
  251.     $entity_type = $field[0]['#entity_type'];
  252.   }
  253.   elseif (isset($form['#entity_type'], $form['#bundle'])) {
  254.     $entity_type = $form['#entity_type'];
  255.     $bundle = $form['#bundle'];
  256.   }
  257.   else {
  258.     return $element;
  259.   }
  260.  
  261.   $dependencies = conditional_fields_load_dependencies($entity_type, $bundle);
  262.  
  263.   if (!$dependencies) {
  264.     return $element;
  265.   }
  266.  
  267.   $field_parents_key = implode(':', $field['#field_parents']);
  268.  
  269.   if (isset($dependencies['dependents'][$field['#field_name']])) {
  270.     // Attach dependent.
  271.     foreach ($dependencies['dependents'][$field['#field_name']] as $id => $dependency) {
  272.       if (!isset($form['#conditional_fields'][$field_parents_key][$field['#field_name']]['dependees'][$id])) {
  273.         conditional_fields_attach_dependency($form, array('#field_name' => $dependency['dependee']), $field, $dependency['options'], $id, $field_parents_key);
  274.       }
  275.     }
  276.   }
  277.  
  278.   // Attach dependee.
  279.   // TODO: collect information about every element of the dependee widget, not
  280.   // just the first encountered. This bottom-up approach would allow us to
  281.   // define per-element sets of dependency values.
  282.   if (isset($dependencies['dependees'][$field['#field_name']])) {
  283.     foreach ($dependencies['dependees'][$field['#field_name']] as $id => $dependency) {
  284.       if (!isset($form['#conditional_fields'][$field_parents_key][$field['#field_name']]['dependents'][$id])) {
  285.         conditional_fields_attach_dependency($form, $field, array('#field_name' => $dependency['dependent']), $dependency['options'], $id, $field_parents_key);
  286.       }
  287.     }
  288.   }
  289.  
  290.   return $element;
  291. }
  292.  
  293. /**
  294.  * Attaches a single dependency to a form.
  295.  *
  296.  * Call this function when defining or altering a form to create dependencies
  297.  * dynamically.
  298.  *
  299.  * @param $form
  300.  *   The form where the dependency is attached.
  301.  * @param $dependee
  302.  *   The dependee field form element. Either a string identifying the element
  303.  *   key in the form, or a fully built field array. Actually used properties of
  304.  *   the array are #field_name and #parents.
  305.  * @param $dependent
  306.  *   The dependent field form element. Either a string identifying the element
  307.  *   key in the form, or a fully built field array. Actually used properties of
  308.  *   the array are #field_name and #field_parents.
  309.  * @param $options
  310.  *   An array of dependency options with the following key/value pairs:
  311.  *   (You don't need to manually set all these options, because default
  312.  *   settings are always provided.)
  313.  *   - state: The state applied to the dependent when the dependency is
  314.  *     triggered. See conditional_fields_states() for available states.
  315.  *   - condition: The condition for the dependency to be triggered. See
  316.  *     conditional_fields_conditions() for available conditions.
  317.  *   - values_set: One of the following constants:
  318.  *     - CONDITIONAL_FIELDS_DEPENDENCY_VALUES_WIDGET: Dependency is
  319.  *       triggered if the dependee has a certain value defined in 'value'.
  320.  *     - CONDITIONAL_FIELDS_DEPENDENCY_VALUES_AND: Dependency is triggered if
  321.  *       the dependee has all the values defined in 'values'.
  322.  *     - CONDITIONAL_FIELDS_DEPENDENCY_VALUES_OR: Dependency is triggered if the
  323.  *       dependee has any of the values defined in 'values'.
  324.  *     - CONDITIONAL_FIELDS_DEPENDENCY_VALUES_XOR: Dependency is triggered if
  325.  *       the dependee has only one of the values defined in 'values'.
  326.  *     - CONDITIONAL_FIELDS_DEPENDENCY_VALUES_NOT: Dependency is triggered if
  327.  *       the dependee does not have any of the values defined in 'values'.
  328.  *   - value: The value to be tested when 'values_set' is
  329.  *     CONDITIONAL_FIELDS_DEPENDENCY_VALUES_WIDGET. An associative array with
  330.  *     the same structure of the dependee field values as found in
  331.  *     $form_states['values] when the form is submitted. You can use
  332.  *     field_default_extract_form_values() to extract this array.
  333.  *   - values: The array of values to be tested when 'values_set' is not
  334.  *     CONDITIONAL_FIELDS_DEPENDENCY_VALUES_WIDGET.
  335.  *   - value_form: An associative array with the same structure of the dependee
  336.  *     field values as found in $form_state['input']['value']['field'] when the
  337.  *     form is submitted.
  338.  *   - effect: The jQuery effect associated to the state change. See
  339.  *     conditional_fields_effects() for available effects and options.
  340.  *   - effect_options: The options for the active effect.
  341.  *   - element_view: An associative array of field view behaviors with
  342.  *     CONDITIONAL_FIELDS_FIELD_VIEW_* constants as keys and the same constants
  343.  *     as values for enabled behaviors and 0 for disabled behaviors.
  344.  *     See conditional_fields_behaviors() for descriptions.
  345.  *   - element_view_per_role: Set to 1 to activate field view settings per role.
  346.  *   - element_view_roles: An associative array of field view settings per role
  347.  *     where the keys are role ids and the values are arrays with the same
  348.  *     structure of 'element_view'.
  349.  *   - element_edit: An associative array of field edit behaviors with
  350.  *     CONDITIONAL_FIELDS_FIELD_EDIT_* constants as keys and the same constants
  351.  *     as values for enabled behaviors and 0 for disabled behaviors.
  352.  *     See conditional_fields_behaviors() for descriptions.
  353.  *   - element_edit_per_role: Set to 1 to activate field edit settings per role.
  354.  *   - element_edit_roles: An associative array of field edit settings per role
  355.  *     where the keys are role ids and the values are arrays with the same
  356.  *     structure of 'element_edit'.
  357.  *   - selector: (optional) Custom jQuery selector for the dependee.
  358.  *   Note that you don't need to manually set all these options, since default
  359.  *   settings are always provided.
  360.  * @param $id
  361.  *   (internal use) The identifier for the dependency. Omit this parameter when
  362.  *   attaching a custom dependency.
  363.  * @param $field_parents_key
  364.  *   (internal use) The field parents structure of the dependent field. Omit
  365.  *   this parameter when attaching a custom dependency.
  366.  */
  367. function conditional_fields_attach_dependency(&$form, $dependee, $dependent, $options, $id = 0, $field_parents_key = '') {
  368.   $options += conditional_fields_dependency_default_options();
  369.  
  370.   // The absence of the $id parameter identifies a custom dependency.
  371.   if (!$id) {
  372.     // String values are accepted to simplify usage of this function with custom
  373.     // forms.
  374.     if (is_string($dependee) && is_string($dependent)) {
  375.       $dependee = array(
  376.         '#field_name' => $dependee,
  377.         '#parents'    => array($dependee),
  378.       );
  379.       $dependent = array(
  380.         '#field_name'    => $dependent,
  381.         '#field_parents' => array($dependent),
  382.       );
  383.  
  384.       // Custom dependencies have automatically assigned a progressive id.
  385.       static $current_id;
  386.       if (!$current_id) {
  387.         $current_id = 1;
  388.       }
  389.       $id = $current_id;
  390.       $current_id++;
  391.     }
  392.  
  393.     if (!$field_parents_key) {
  394.       $field_parents_key = implode(':', $dependent['#field_parents']);
  395.     }
  396.   }
  397.  
  398.   // Attach dependee.
  399.   // Use the #array_parents property of the dependee instead of #field_parents
  400.   // since we will need access to the full structure of the widget.
  401.   if (isset($dependee['#parents'])) {
  402.     $form['#conditional_fields'][$field_parents_key][$dependee['#field_name']]['parents'] = $dependee['#array_parents'];
  403.     $form['#conditional_fields'][$field_parents_key][$dependee['#field_name']]['dependents'][$id] = array(
  404.       'dependent' => $dependent['#field_name'],
  405.       'options'   => $options,
  406.     );
  407.   }
  408.  
  409.   // Attach dependent.
  410.   if (isset($dependent['#field_parents'])) {
  411.     $dependent_parents = $dependent['#field_parents'];
  412.   }
  413.   elseif (isset($dependent['#parents'])) {
  414.     $dependent_parents = $dependent['#parents'];
  415.   }
  416.   if (isset($dependent_parents)) {
  417.     $form['#conditional_fields'][$field_parents_key][$dependent['#field_name']]['field_parents'] = $dependent_parents;
  418.     $form['#conditional_fields'][$field_parents_key][$dependent['#field_name']]['dependees'][$id] = array(
  419.       'dependee' => $dependee['#field_name'],
  420.       'options'  => $options,
  421.     );
  422.   }
  423.  
  424.   // Actual processing is done in conditional_fields_form_after_build().
  425.   // Append the property so the callback runs last.
  426.   _conditional_fields_element_add_property($form, '#after_build', 'conditional_fields_form_after_build', 'append');
  427. }
  428.  
  429. /**
  430.  * after_build callback for forms with dependencies.
  431.  *
  432.  * Builds and attaches #states properties to dependent fields, adds additional
  433.  * visual effects handling to the States API and attaches a validation callback
  434.  * to the form that handles validation of dependent fields.
  435.  */
  436. function conditional_fields_form_after_build($form, &$form_state) {
  437.   // Dependencies data is attached in conditional_fields_element_after_build().
  438.   if (empty($form['#conditional_fields'])) {
  439.     return $form;
  440.   }
  441.  
  442.   $effects = array();
  443.   $state_handlers = conditional_fields_states_handlers();
  444.  
  445.   // Cycle all dependents.
  446.   foreach ($form['#conditional_fields'] as $parent_dependent_key => $parent_dependent_info) {
  447.     foreach ($parent_dependent_info as $dependent => $dependent_info) {
  448.       $states = array();
  449.  
  450.       if (empty($dependent_info['dependees'])) {
  451.         continue;
  452.       }
  453.  
  454.       $dependent_location = array_merge($dependent_info['field_parents'], array($dependent));
  455.       $dependent_form_field = drupal_array_get_nested_value($form, $dependent_location);
  456.  
  457.       // Cycle the dependant's dependees.
  458.       foreach ($dependent_info['dependees'] as $dependency) {
  459.         $dependee = $dependency['dependee'];
  460.  
  461.         if (empty($form['#conditional_fields'][$parent_dependent_key][$dependee])) {
  462.           continue;
  463.         }
  464.  
  465.         $dependee_info = $form['#conditional_fields'][$parent_dependent_key][$dependee];
  466.         $dependee_form_field = drupal_array_get_nested_value($form, $dependee_info['parents']);
  467.         $options = $dependency['options'];
  468.  
  469.         // Load field edit behaviors.
  470.         // If this dependent has multiple dependees, only the logic of the first
  471.         // dependency will be taken into account.
  472.         if (!isset($behaviors)) {
  473.           $behaviors = conditional_fields_field_behaviors('edit', $options);
  474.         }
  475.  
  476.         // Determine if the dependee is in the form.
  477.         if (empty($dependee_form_field) || (isset($dependee_form_field['#access']) && $dependee_form_field['#access'] == FALSE)) {
  478.           // Apply orphan dependent behaviors.
  479.           /*
  480.           if (in_array(CONDITIONAL_FIELDS_FIELD_EDIT_HIDE_UNTRIGGERED_ORPHAN, $behaviors)) {
  481.             // TODO
  482.             $is_triggered = TRUE;
  483.  
  484.             if ($is_orphan && !$is_triggered) {
  485.               $form[$dependent]['#access'] = FALSE;
  486.             }
  487.           }
  488.           */
  489.           if (in_array(CONDITIONAL_FIELDS_FIELD_EDIT_HIDE_ORPHAN, $behaviors)) {
  490.             $dependent_form_field['#access'] = FALSE;
  491.           }
  492.           unset($behaviors[CONDITIONAL_FIELDS_FIELD_EDIT_HIDE_UNTRIGGERED_ORPHAN]);
  493.           unset($behaviors[CONDITIONAL_FIELDS_FIELD_EDIT_HIDE_ORPHAN]);
  494.           unset($behaviors[CONDITIONAL_FIELDS_FIELD_EDIT_RESET_UNTRIGGERED]);
  495.           continue;
  496.         }
  497.  
  498.         unset($behaviors[CONDITIONAL_FIELDS_FIELD_EDIT_HIDE_UNTRIGGERED_ORPHAN]);
  499.         unset($behaviors[CONDITIONAL_FIELDS_FIELD_EDIT_HIDE_ORPHAN]);
  500.  
  501.         // Build a jQuery selector if it was not overridden by a custom value.
  502.         // Note that this may be overridden later by a state handler.
  503.         if (!$options['selector']) {
  504.           $options['selector'] = conditional_fields_field_selector($dependee_form_field);
  505.         }
  506.         else {
  507.           // Replace the language placeholder in the selector with current language.
  508.           $options['selector'] = str_replace('%lang', $dependee_form_field['#language'], $options['selector']);
  509.         }
  510.  
  511.         if ($options['condition'] != 'value') {
  512.           // Conditions different than "value" are always evaluated against TRUE.
  513.           $state = array($options['state'] => array($options['selector'] => array($options['condition'] => TRUE)));
  514.         }
  515.         else {
  516.           // Build the values that trigger the dependency.
  517.           $values = array();
  518.  
  519.           if ($options['values_set'] == CONDITIONAL_FIELDS_DEPENDENCY_VALUES_WIDGET) {
  520.             $values[$options['condition']] = $options['value_form'];
  521.           }
  522.           elseif ($options['values_set'] == CONDITIONAL_FIELDS_DEPENDENCY_VALUES_REGEX) {
  523.             $values[$options['condition']] = $options['value'];
  524.           }
  525.           elseif ($options['values_set'] == CONDITIONAL_FIELDS_DEPENDENCY_VALUES_AND) {
  526.             $values[$options['condition']] = count($options['values']) == 1 ? $options['values'][0] : $options['values'];
  527.           }
  528.           else {
  529.             if ($options['values_set'] == CONDITIONAL_FIELDS_DEPENDENCY_VALUES_XOR) {
  530.               // XOR behaves like OR with added 'xor' element.
  531.               $values[] = 'xor';
  532.             }
  533.             elseif ($options['values_set'] == CONDITIONAL_FIELDS_DEPENDENCY_VALUES_NOT) {
  534.               // NOT behaves like OR with switched state.
  535.               $options['state'] = strpos($options['state'], '!') === 0 ? drupal_substr($options['state'], 1) : '!' . $options['state'];
  536.             }
  537.  
  538.             // OR, NOT and XOR conditions are obtained with a nested array.
  539.             foreach ($options['values'] as $value) {
  540.               $values[] = array($options['condition'] => $value);
  541.             }
  542.           }
  543.  
  544.           $state = array($options['state'] => array($options['selector'] => $values));
  545.           $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;
  546.  
  547.           // Execute special handler for fields that need further processing.
  548.           // The handler has no return value. Modify the $state parameter by
  549.           // reference if needed.
  550.           foreach ($state_handlers as $handler => $handler_conditions) {
  551.             if (array_intersect_assoc($handler_conditions, $dependee_form_field) == $handler_conditions) {
  552.               $handler($dependee_form_field, $dependee_form_state, $options, $state);
  553.             }
  554.           }
  555.         }
  556.  
  557.         // Add validation callback to element if the dependency can be evaluated.
  558.         if (in_array($options['condition'], array('value', 'empty', '!empty'))) {
  559.           _conditional_fields_element_add_property($dependent_form_field, '#element_validate', 'conditional_fields_dependent_validate', 'append');
  560.         }
  561.  
  562.         if (isset($state)) {
  563.           // Add the $state into the correct logic group in $states.
  564.           foreach ($state as $key => $constraints) {
  565.             foreach ($constraints as $selector => $constraint) {
  566.               // Add the constraint in an array to avoid overwriting other
  567.               // dependencies' states with the same selector.
  568.               $states[$key][$options['grouping']][$selector][] = $constraint;
  569.             }
  570.  
  571.             // Build effect settings for effects with options.
  572.             // TODO: add dependee key to allow different effects on the same selector.
  573.             if ($options['effect'] && $options['effect'] != 'show') {
  574.               $selector = conditional_fields_field_selector(drupal_array_get_nested_value($form, array($dependent_location[0])));
  575.               // Convert numeric strings to numbers.
  576.               foreach ($options['effect_options'] as &$effect_option) {
  577.                 if (is_numeric($effect_option)) {
  578.                   $effect_option += 0;
  579.                 }
  580.               }
  581.               $effects[$selector] = array(
  582.                 'effect' => $options['effect'],
  583.                 'options' => $options['effect_options'],
  584.               );
  585.             }
  586.  
  587.             // Apply reset dependent to default if untriggered behavior.
  588.             if (in_array(CONDITIONAL_FIELDS_FIELD_EDIT_RESET_UNTRIGGERED, $behaviors)) {
  589.               // Add property to element so conditional_fields_dependent_validate() can
  590.               // pick it up.
  591.               $dependent_form_field['#conditional_fields_reset_if_untriggered'] = TRUE;
  592.               unset($behaviors[CONDITIONAL_FIELDS_FIELD_EDIT_RESET_UNTRIGGERED]);
  593.             }
  594.           }
  595.         }
  596.       }
  597.  
  598.       // Execute custom behaviors callbacks.
  599.       if (!empty($behaviors)) {
  600.         foreach ($behaviors as $behavior) {
  601.           $behavior($form, $form_state, $dependent, $dependent_info);
  602.         }
  603.       }
  604.  
  605.       unset($behaviors);
  606.  
  607.       if (empty($states)) {
  608.         continue;
  609.       }
  610.  
  611.       // Save the modified field back into the form.
  612.       drupal_array_set_nested_value($form, $dependent_location, $dependent_form_field);
  613.  
  614.       // Map the states based on the conjunctions.
  615.       $states_new = array();
  616.       foreach ($states as $state_key => $value) {
  617.         // As the main object is ANDed together we can add the AND items directly.
  618.         if (!empty($states[$state_key]['AND'])) {
  619.           $states_new[$state_key] = $states[$state_key]['AND'];
  620.         }
  621.         // The OR and XOR groups are moved into a sub-array that has numeric keys
  622.         // so that we get a JSON array and not an object, as required by the States
  623.         // API for OR and XOR groupings.
  624.         if (!empty($states[$state_key]['OR'])) {
  625.           $or = array();
  626.           foreach ($states[$state_key]['OR'] as $constraint_key => $constraint_value) {
  627.             $or[] = array($constraint_key => $constraint_value);
  628.           }
  629.           // '1' as a string so that we get an object (which means logic groups
  630.           // are ANDed together).
  631.           $states_new[$state_key]['1'] = $or;
  632.         }
  633.         if (!empty($states[$state_key]['XOR'])) {
  634.           $xor = array('xor');
  635.           foreach ($states[$state_key]['XOR'] as $constraint_key => $constraint_value) {
  636.             $xor[] = array($constraint_key => $constraint_value);
  637.           }
  638.           // '2' as a string so that we get an object.
  639.           $states_new[$state_key]['2'] = $xor;
  640.         }
  641.       }
  642.       $states = $states_new;
  643.  
  644.       // Add the #states property to the dependent field.
  645.       drupal_array_set_nested_value($form, array_merge($dependent_location, array('#states')), $states);
  646.  
  647.       $has_states = TRUE;
  648.     }
  649.   }
  650.  
  651.   if (empty($has_states)) {
  652.     return $form;
  653.   }
  654.  
  655.   $form['#attached']['library'][] = array('conditional_fields', 'conditional_fields');
  656.  
  657.   // Add effect settings to the form.
  658.   if ($effects) {
  659.     $form['#attached']['js'][] = array(
  660.       'data' => array(
  661.         'conditionalFields' => array(
  662.           'effects' => $effects,
  663.         ),
  664.       ),
  665.       'type' => 'setting',
  666.     );
  667.   }
  668.  
  669.   // Validation callback to manage dependent fields validation.
  670.   $form['#validate'][] = 'conditional_fields_form_validate';
  671.   // Initialize validation information every time the form is rendered to avoid
  672.   // stale data after a failed submission.
  673.   $form_state['conditional_fields_untriggered_dependents'] = array();
  674.  
  675.   return $form;
  676. }
  677.  
  678. /**
  679.  * Dependent field validation callback.
  680.  *
  681.  * If the dependencies of a dependent field are not triggered, the validation
  682.  * errors that it might have thrown must be removed, together with its submitted
  683.  * values. This will simulate the field not being present in the form at all.
  684.  * In this field-level callback we just collect needed information and store it
  685.  * in $form_state. Values and errors will be removed in a single sweep in
  686.  * conditional_fields_form_validate(), which runs at the end of the validation
  687.  * cycle.
  688.  *
  689.  * @see conditional_fields_form_validate()
  690.  */
  691. function conditional_fields_dependent_validate($element, &$form_state, $form) {
  692.   $dependent = $element[$element['#language']];
  693.  
  694.   // Check if this field's dependencies were triggered.
  695.   if (conditional_fields_evaluate_dependencies($dependent, $form, $form_state)) {
  696.     return;
  697.   }
  698.  
  699.   // Mark submitted values for removal. We have to remove them after all fields
  700.   // have been validated to avoid collision between dependencies.
  701.   $form_state_addition['parents'] = $dependent['#array_parents'];
  702.  
  703.   // Optional behavior: reset the field to its default values.
  704.   // Default values are always valid, so it's safe to skip validation.
  705.   if (!empty($element['#conditional_fields_reset_if_untriggered'])) {
  706.     $form_state_addition['reset'] = TRUE;
  707.   }
  708.  
  709.   // Tag validation errors previously set on this field for removal in
  710.   // conditional_fields_form_validate().
  711.   $errors = form_get_errors();
  712.  
  713.   if ($errors) {
  714.     $error_key = implode('][', $dependent['#parents']);
  715.     foreach ($errors as $name => $error) {
  716.       // An error triggered by this field might have been set on a descendant
  717.       // element. This also means that so there can be multiple errors on the
  718.       // same field (even though Drupal doesn't support multiple errors on the
  719.       // same element).
  720.       if (strpos($name, $error_key) === 0) {
  721.         $field_errors[$name] = $error;
  722.       }
  723.     }
  724.   }
  725.  
  726.   if (!empty($field_errors)) {
  727.     $form_state_addition['errors'] = $field_errors;
  728.   }
  729.  
  730.   $form_state['conditional_fields_untriggered_dependents'][] = $form_state_addition;
  731. }
  732.  
  733. /**
  734.  * Extracts field values from a field element of a submitted form.
  735.  *
  736.  * @return
  737.  *   The requested field values parent. Actual field vales are stored under the
  738.  *   key $element['#field_name'].
  739.  */
  740. function conditional_fields_field_get_values($element, $form_state) {
  741.   // Fall back to #parents to support custom dependencies.
  742.   $parents = !empty($element['#field_parents']) ? $element['#field_parents'] : $element['#parents'];
  743.   return drupal_array_get_nested_value($form_state['values'], $parents);
  744. }
  745.  
  746. /**
  747.  * Validation callback for any form with conditional fields.
  748.  *
  749.  * This validation callback is added to all forms that contain fields with
  750.  * dependencies. It removes all validation errors from dependent fields whose
  751.  * dependencies are not triggered, which were collected at field-level
  752.  * validation in conditional_fields_dependent_validate().
  753.  *
  754.  * @see conditional_fields_dependent_validate()
  755.  */
  756. function conditional_fields_form_validate($form, &$form_state) {
  757.   if (empty($form_state['conditional_fields_untriggered_dependents'])) {
  758.     return;
  759.   }
  760.  
  761.   $untriggered_dependents_errors = array();
  762.  
  763.   foreach ($form_state['conditional_fields_untriggered_dependents'] as $field) {
  764.     $dependent = drupal_array_get_nested_value($form, $field['parents']);
  765.     $field_values_location = conditional_fields_field_get_values($dependent, $form_state);
  766.  
  767.     // If we couldn't find a location for the field's submitted values, let the
  768.     // validation errors pass through to avoid security holes.
  769.     if (!isset($field_values_location[$dependent['#field_name']])) {
  770.       if (!empty($field['errors'])) {
  771.         $untriggered_dependents_errors = array_merge($untriggered_dependents_errors, $field['errors']);
  772.       }
  773.       continue;
  774.     }
  775.  
  776.     if (empty($field['reset'])) {
  777.       unset($field_values_location[$dependent['#field_name']]);
  778.     }
  779.     else {
  780.       $dependent_info = field_form_get_state($dependent['#field_parents'], $dependent['#field_name'], $dependent['#language'], $form_state);
  781.       $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']);
  782.     }
  783.  
  784.     // Save the changed array back in place.
  785.     // Do not use form_set_value() since it assumes that the values are located at
  786.     // $form_state['values'][ ... $element['#parents'] ... ], while the
  787.     // documentation of hook_field_widget_form() states that field values are
  788.     // $form_state['values'][ ... $element['#field_parents'] ... ].
  789.     drupal_array_set_nested_value($form_state['values'], $dependent['#field_parents'], $field_values_location);
  790.  
  791.     if (!empty($field['errors'])) {
  792.       $untriggered_dependents_errors = array_merge($untriggered_dependents_errors, $field['errors']);
  793.     }
  794.   }
  795.  
  796.   if (!empty($untriggered_dependents_errors)) {
  797.     // Since Drupal provides no clean way to selectively remove error messages,
  798.     // we have to store all current form errors and error messages, clear them,
  799.     // filter out from our stored values the errors originating from untriggered
  800.     // dependent fields, and then reinstate remaining errors and messages.
  801.     $errors = array_diff_assoc((array) form_get_errors(), $untriggered_dependents_errors);
  802.     form_clear_error();
  803.     $error_messages = drupal_get_messages('error');
  804.     $removed_messages = array_values($untriggered_dependents_errors);
  805.  
  806.     // Reinstate remaining errors.
  807.     foreach ($errors as $name => $error) {
  808.       form_set_error($name, $error);
  809.       // form_set_error() calls drupal_set_message(), so we have to filter out
  810.       // these from the messages to avoid duplicates.
  811.       $removed_messages[] = $error;
  812.     }
  813.  
  814.     // Reinstate remaining error messages (which, at this point, are messages that
  815.     // were originated outside of the validation process).
  816.     foreach (array_diff($error_messages['error'], $removed_messages) as $message) {
  817.       drupal_set_message($message, 'error');
  818.     }
  819.   }
  820. }
  821.  
  822. /**
  823.  * Helper function to add a property/value pair to a render array safely without
  824.  * overriding any pre-existing value.
  825.  *
  826.  * @param $position
  827.  *   'append' if $value should be inserted at the end of the $element[$property]
  828.  *   array, any other value to insert it at the beginning.
  829.  *
  830.  */
  831. function _conditional_fields_element_add_property(&$element, $property, $value, $position = 'prepend') {
  832.   // Avoid overriding default element properties that might not yet be set.
  833.   if (!isset($element[$property])) {
  834.     $element[$property] = isset($element['#type']) ? element_info_property($element['#type'], $property, array()) : array();
  835.   }
  836.  
  837.   if (in_array($value, $element[$property])) {
  838.     return;
  839.   }
  840.  
  841.   switch ($position) {
  842.     case 'append':
  843.       $element[$property] = array_merge($element[$property], (array) $value);
  844.       break;
  845.  
  846.     case 'prepend':
  847.     default:
  848.       $element[$property] = array_merge((array) $value, $element[$property]);
  849.       break;
  850.   }
  851. }
  852.  
  853. /**
  854.  * Implements hook_entity_view_alter().
  855.  *
  856.  * Applies entity view logic to conditional fields.
  857.  */
  858. function conditional_fields_entity_view_alter(&$build, $type) {
  859.   if (!(isset($build['#entity_type'], $build['#bundle']) && $dependencies = conditional_fields_load_dependencies($build['#entity_type'], $build['#bundle']))) {
  860.     return;
  861.   }
  862.  
  863.   $evaluated_dependents = array();
  864.  
  865.   foreach ($dependencies['dependents'] as $dependent => $dependency) {
  866.     if (empty($build[$dependent]['#access'])) {
  867.       continue;
  868.     }
  869.  
  870.     foreach ($dependency as $dependency_options) {
  871.       $dependee = $dependency_options['dependee'];
  872.       $options = $dependency_options['options'];
  873.  
  874.       // We can interface with the States API only through the Value condition.
  875.       if ($options['condition'] != 'value') {
  876.         continue;
  877.       }
  878.  
  879.       // Determine field view behaviors.
  880.       // If this dependent has multiple dependencies, only the logic of the
  881.       // first dependency will be taken into account.
  882.       if (!isset($behaviors)) {
  883.         $behaviors = conditional_fields_field_behaviors('view', $options);
  884.       }
  885.  
  886.       // Manage orphan fields (dependents with no dependees).
  887.       $evaluate = in_array(CONDITIONAL_FIELDS_FIELD_VIEW_EVALUATE, $behaviors);
  888.       $hide_orphan = in_array(CONDITIONAL_FIELDS_FIELD_VIEW_HIDE_ORPHAN, $behaviors);
  889.       $hide_untriggered_orphan = in_array(CONDITIONAL_FIELDS_FIELD_VIEW_HIDE_UNTRIGGERED_ORPHAN, $behaviors);
  890.       $is_orphan = empty($build[$dependee]['#access']);
  891.       if ($is_orphan) {
  892.         // Hide the dependent. No need to evaluate the dependency.
  893.         if ($hide_orphan) {
  894.           $build[$dependent]['#access'] = FALSE;
  895.           continue;
  896.         }
  897.         if ($hide_untriggered_orphan) {
  898.           $evaluate = TRUE;
  899.         }
  900.         if ($evaluate) {
  901.           // We have to look for the dependee in the entity object.
  902.           // TODO: Is it possible to avoid hardcoding this?
  903.           switch ($type) {
  904.             case 'node':
  905.               $entity_property = '#node';
  906.               break;
  907.             case 'user':
  908.               $entity_property = '#account';
  909.               break;
  910.             case 'term':
  911.               $entity_property = '#term';
  912.               break;
  913.             case 'field_collection_item':
  914.             case 'profile2':
  915.             default:
  916.               $entity_property = '#entity';
  917.           }
  918.           // If we didn't find the field, there is nothing more we can do.
  919.           if (!isset($build[$entity_property]->$dependee)) {
  920.             continue;
  921.           }
  922.           $items = $build[$entity_property]->$dependee;
  923.           // Values are keyed by language here, remove it.
  924.           $items = array_shift($items);
  925.         }
  926.       }
  927.       else {
  928.         $items = $build[$dependee]['#items'];
  929.       }
  930.  
  931.       if ($evaluate) {
  932.         $evaluated_dependents[$dependent][$options['grouping']][] = conditional_fields_evaluate_dependency('view', $items, $options);
  933.       }
  934.     }
  935.     if (isset($evaluated_dependents[$dependent])) {
  936.       $is_triggered = conditional_fields_evaluate_grouping($evaluated_dependents[$dependent]);
  937.  
  938.       foreach ($behaviors as $behavior) {
  939.         switch ($behavior) {
  940.           case CONDITIONAL_FIELDS_FIELD_VIEW_EVALUATE:
  941.             // Hide the dependent if it is not triggered.
  942.             if (!$is_triggered) {
  943.               $build[$dependent]['#access'] = FALSE;
  944.             }
  945.             break;
  946.  
  947.           case CONDITIONAL_FIELDS_FIELD_VIEW_HIDE_ORPHAN:
  948.             // This case was handled while looking for the field.
  949.             break;
  950.  
  951.           case CONDITIONAL_FIELDS_FIELD_VIEW_HIDE_UNTRIGGERED_ORPHAN:
  952.             // Hide the dependent if the dependee is not viewable and the dependency is not triggered.
  953.             if ($is_orphan && !$is_triggered) {
  954.               $build[$dependent]['#access'] = FALSE;
  955.             }
  956.             break;
  957.  
  958.           case CONDITIONAL_FIELDS_FIELD_VIEW_HIGHLIGHT:
  959.             // Show the dependent themed like an error message if it is not triggered.
  960.             if (!$is_triggered) {
  961.               $build[$dependent]['#prefix'] = isset($build[$dependent]['#prefix']) ? '<div class="messages error">' . $build[$dependent]['#prefix'] : '<div class="messages error">';
  962.               $build[$dependent]['#suffix'] = isset($build[$dependent]['#suffix']) ? $build[$dependent]['#suffix'] . '</div>' : '</div>';
  963.             }
  964.             break;
  965.  
  966.           case CONDITIONAL_FIELDS_FIELD_VIEW_DESCRIBE:
  967.             // Show a textual description of the dependency under the dependent field.
  968.             if ($build[$dependent]['#access']) {
  969.               $dependee_title = isset($build[$dependee]['#title']) ? $build[$dependee]['#title'] : $dependee;
  970.               $dependent_title = isset($build[$dependent]['#title']) ? $build[$dependent]['#title'] : $dependent;
  971.               $description = conditional_fields_dependency_description($dependee_title, $dependent_title, $options);
  972.               if (isset($build[$dependent]['#suffix'])) {
  973.                 $description = $build[$dependent]['#suffix'] . $description;
  974.               }
  975.               $build[$dependent]['#suffix'] = $description;
  976.             }
  977.             break;
  978.  
  979.           default:
  980.             // Custom behaviors are callbacks.
  981.             $behavior($dependee, $dependent, $is_triggered, $dependencies, $build, $type);
  982.             break;
  983.         }
  984.  
  985.         if (empty($build[$dependent]['#access'])) {
  986.           break;
  987.         }
  988.       }
  989.     }
  990.  
  991.     unset($behaviors);
  992.   }
  993. }
  994.  
  995. /**
  996.  * Evaluates an array with 'AND', 'OR' and 'XOR' groupings,
  997.  * each containing a list of boolean values.
  998.  */
  999. function conditional_fields_evaluate_grouping($groups) {
  1000.   $or = $and = $xor = TRUE;
  1001.   if (!empty($groups['OR'])) {
  1002.     $or = in_array(TRUE, $groups['OR']);
  1003.   }
  1004.   if (!empty($groups['AND'])) {
  1005.     $and = !in_array(FALSE, $groups['AND']);
  1006.   }
  1007.   if (!empty($groups['XOR'])) {
  1008.     $xor = array_sum($groups['XOR']) == 1;
  1009.   }
  1010.   return $or && $and && $xor;
  1011. }
  1012.  
  1013. /**
  1014.  * Evaluate a set of dependencies for a dependent field.
  1015.  *
  1016.  * @param $dependent
  1017.  *   The field form element in the current language.
  1018.  */
  1019. function conditional_fields_evaluate_dependencies($dependent, $form, $form_state) {
  1020.   $field_parents_key = implode(':', $dependent['#field_parents']);
  1021.   $dependencies = $form['#conditional_fields'][$field_parents_key][$dependent['#field_name']]['dependees'];
  1022.   $evaluated_dependees = array();
  1023.  
  1024.   foreach ($dependencies as $dependency_id => $dependency) {
  1025.     // Skip dependencies that can't be evaluated.
  1026.     if (!in_array($dependency['options']['condition'], array('value', 'empty', '!empty'))) {
  1027.       continue;
  1028.     }
  1029.  
  1030.     $values = conditional_fields_field_form_get_values($field_parents_key, $dependency['dependee'], $form, $form_state);
  1031.  
  1032.     $evaluated_dependees[$dependent['#field_name']][$dependency['options']['grouping']][] = conditional_fields_evaluate_dependency('edit', $values, $dependency['options']);
  1033.   }
  1034.  
  1035.   return conditional_fields_evaluate_grouping($evaluated_dependees[$dependent['#field_name']]);
  1036. }
  1037.  
  1038. /**
  1039.  * Extracts field values from a submitted form.
  1040.  */
  1041. function conditional_fields_field_form_get_values($field_parents_key, $field_name, $form, $form_state) {
  1042.   if (empty($form['#conditional_fields'][$field_parents_key][$field_name])) {
  1043.     return array();
  1044.   }
  1045.  
  1046.   $field_parents = $form['#conditional_fields'][$field_parents_key][$field_name]['parents'];
  1047.  
  1048.   // We have the parents of the field, but depending on the entity type and
  1049.   // the widget type, they may include additional elements that are actually
  1050.   // part of the value. So we find the depth of the field inside the form
  1051.   // structure and use the parents only up to that depth.
  1052.   $field_parents_keys = array_flip($field_parents);
  1053.   $field_parent = drupal_array_get_nested_value($form, array_slice($field_parents, 0, $field_parents_keys[$field_name]));
  1054.   $values = conditional_fields_field_get_values($field_parent[$field_name], $form_state);
  1055.  
  1056.   // Remove the language key.
  1057.   if (isset($field_parent[$field_name]['#language'], $values[$field_parent[$field_name]['#language']])) {
  1058.     $values = $values[$field_parent[$field_name]['#language']];
  1059.   }
  1060.  
  1061.   return $values;
  1062. }
  1063.  
  1064. /**
  1065.  * Evaluate if a dependency meets the requirements to be triggered.
  1066.  *
  1067.  * @param $context
  1068.  *   'edit' if $values are extracted from $form_state or 'view' if
  1069.  *   $values are extracted from an entity.
  1070.  */
  1071. function conditional_fields_evaluate_dependency($context, $values, $options) {
  1072.   if ($options['condition'] == 'empty') {
  1073.     $options['values_set'] = CONDITIONAL_FIELDS_DEPENDENCY_VALUES_AND;
  1074.     $options['values'] = array('');
  1075.   }
  1076.   elseif ($options['condition'] == '!empty') {
  1077.     $options['values_set'] = CONDITIONAL_FIELDS_DEPENDENCY_VALUES_NOT;
  1078.     $options['values'] = array('');
  1079.   }
  1080.  
  1081.   if ($options['values_set'] == CONDITIONAL_FIELDS_DEPENDENCY_VALUES_WIDGET) {
  1082.     $dependency_values = $context == 'view' ? $options['value'] : $options['value_form'];
  1083.  
  1084.     // Simple case: both values are strings or integers. Should never happen in
  1085.     // view context, but does no harm to check anyway.
  1086.     if (!is_array($values)) {
  1087.       // Options elements consider "_none" value same as empty.
  1088.       $values = $values === '_none' ? '' : $values;
  1089.  
  1090.       if (!is_array($dependency_values)) {
  1091.         // Some widgets store integers, but values saved in $dependency_values
  1092.         // are always strings. Convert them to integers because we want to do a
  1093.         // strict equality check to differentiate empty strings from '0'.
  1094.         if (is_int($values) && is_numeric($dependency_values)) {
  1095.           $dependency_values = (int) $dependency_values;
  1096.         }
  1097.  
  1098.         return $dependency_values === $values;
  1099.       }
  1100.  
  1101.       // If $values is a string and $dependency_values an array, convert $values
  1102.       // to the standard field array form format. This covers cases like single
  1103.       // value textfields.
  1104.       $values = array(array('value' => $values));
  1105.     }
  1106.  
  1107.     // If we are in form context, we are almost done.
  1108.     if ($context == 'edit') {
  1109.       // If $dependency_values is not an array, we can only assume that it
  1110.       // should map to the first key of the first value of $values.
  1111.       if (!is_array($dependency_values)) {
  1112.         $key = current(array_keys((array) current($values)));
  1113.         $dependency_values = array(array($key => $dependency_values));
  1114.       }
  1115.  
  1116.       // Compare arrays recursively ignoring keys, since multiple select widgets
  1117.       // values have numeric keys in form format and string keys in storage
  1118.       // format.
  1119.       return array_values($dependency_values) == array_values($values);
  1120.     }
  1121.  
  1122.     // $values, when viewing fields, may contain all sort of additional
  1123.     // information, so filter out from $values the keys that are not present in
  1124.     // $dependency_values.
  1125.     // Values here are alway keyed by delta (regardless of multiple value
  1126.     // settings).
  1127.     foreach ($values as $delta => &$value) {
  1128.       if (isset($dependency_values[$delta])) {
  1129.         $value = array_intersect_key($value, $dependency_values[$delta]);
  1130.  
  1131.         foreach ($value as $key => &$element_value) {
  1132.           if (isset($dependency_values[$delta][$key]) && is_int($dependency_values[$delta][$key]) && is_numeric($element_value)) {
  1133.             $element_value = (int) $element_value;
  1134.           }
  1135.         }
  1136.       }
  1137.     }
  1138.  
  1139.     // Compare values.
  1140.     foreach ($dependency_values as $delta => $dependency_value) {
  1141.       if (!isset($values[$delta])) {
  1142.         return FALSE;
  1143.       }
  1144.       foreach ($dependency_value as $key => $dependency_element_value) {
  1145.         // Ignore keys set in the field and not in the dependency.
  1146.         if (isset($values[$delta][$key]) && $values[$delta][$key] !== $dependency_element_value) {
  1147.           return FALSE;
  1148.         }
  1149.       }
  1150.     }
  1151.  
  1152.     return TRUE;
  1153.   }
  1154.  
  1155.   // Flatten array of values.
  1156.   $reference_values = array();
  1157.   foreach ((array) $values as $value) {
  1158.     // TODO: support multiple values.
  1159.     $reference_values[] = is_array($value) ? array_shift($value) : $value;
  1160.   }
  1161.  
  1162.   // Regular expression method.
  1163.   if ($options['values_set'] == CONDITIONAL_FIELDS_DEPENDENCY_VALUES_REGEX) {
  1164.     foreach ($reference_values as $reference_value) {
  1165.       if (!preg_match('/' . $options['value']['RegExp'] . '/', $reference_value)) {
  1166.         return FALSE;
  1167.       }
  1168.     }
  1169.     return TRUE;
  1170.   }
  1171.  
  1172.   switch ($options['values_set']) {
  1173.     case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_AND:
  1174.       $diff = array_diff($options['values'], $reference_values);
  1175.       return empty($diff);
  1176.  
  1177.     case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_OR:
  1178.       $intersect = array_intersect($options['values'], $reference_values);
  1179.       return !empty($intersect);
  1180.  
  1181.     case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_XOR:
  1182.       $intersect = array_intersect($options['values'], $reference_values);
  1183.       return count($intersect) == 1;
  1184.  
  1185.     case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_NOT:
  1186.       $intersect = array_intersect($options['values'], $reference_values);
  1187.       return empty($intersect);
  1188.   }
  1189. }
  1190.  
  1191. /**
  1192.  * Determine which dependency behaviors should be used in forms and content
  1193.  * display, depending on dependency options and user roles.
  1194.  *
  1195.  * @param $op
  1196.  *   'view' or 'edit'.
  1197.  * @param $options
  1198.  *    Dependency options.
  1199.  *
  1200.  * @return
  1201.  *   An array of behaviors.
  1202.  *
  1203.  */
  1204. function conditional_fields_field_behaviors($op, $options) {
  1205.   global $user;
  1206.  
  1207.   if ($options['element_' . $op . '_per_role']) {
  1208.     foreach ($options['element_' . $op . '_roles'] as $rid => $role_behaviors) {
  1209.       if (isset($user->roles[$rid])) {
  1210.         $behaviors = $role_behaviors;
  1211.         break;
  1212.       }
  1213.     }
  1214.   }
  1215.   else {
  1216.     $behaviors = $options['element_' . $op];
  1217.   }
  1218.  
  1219.   // Filter out inactive behaviors.
  1220.   $behaviors = array_filter($behaviors);
  1221.  
  1222.   return $behaviors;
  1223. }
  1224.  
  1225. /**
  1226.  * Builds a jQuery selector from the name or id attribute of a field.
  1227.  *
  1228.  * @todo support custom selectors with %lang and %key placeholders.
  1229.  *
  1230.  * @param $field
  1231.  *   The field form element.
  1232.  *
  1233.  * @return
  1234.  *   A jQuery selector string.
  1235.  */
  1236. function conditional_fields_field_selector($field) {
  1237.   if (isset($field['#attributes']['name'])) {
  1238.     return '[name="' . $field['#attributes']['name'] . '"]';
  1239.   }
  1240.  
  1241.   if (isset($field['#name'])) {
  1242.     return '[name="' . $field['#name'] . '"]';
  1243.   }
  1244.  
  1245.   // Try with id if name is not found.
  1246.   if (isset($field['#attributes']['id'])) {
  1247.     return '#' . $field['#attributes']['id'];
  1248.   }
  1249.  
  1250.   if (isset($field['#id'])) {
  1251.     return '#' . $field['#id'];
  1252.   }
  1253.  
  1254.   return FALSE;
  1255. }
  1256.  
  1257. /**
  1258.  * Provides default options for a dependency.
  1259.  *
  1260.  * For an explanation of available options,
  1261.  * @see conditional_fields_field_attach_dependency()
  1262.  */
  1263. function conditional_fields_dependency_default_options() {
  1264.   return array(
  1265.     'state'                 => 'visible',
  1266.     'condition'             => 'value',
  1267.     'grouping'              => 'AND',
  1268.     'values_set'            => CONDITIONAL_FIELDS_DEPENDENCY_VALUES_WIDGET,
  1269.     'value'                 => array(),
  1270.     'values'                => array(),
  1271.     'value_form'            => array(),
  1272.     'effect'                => 'show',
  1273.     'effect_options'        => array(),
  1274.     'element_view'          => array(
  1275.       CONDITIONAL_FIELDS_FIELD_VIEW_EVALUATE => CONDITIONAL_FIELDS_FIELD_VIEW_EVALUATE,
  1276.       CONDITIONAL_FIELDS_FIELD_VIEW_HIDE_ORPHAN => CONDITIONAL_FIELDS_FIELD_VIEW_HIDE_ORPHAN,
  1277.       CONDITIONAL_FIELDS_FIELD_VIEW_HIDE_UNTRIGGERED_ORPHAN => 0,
  1278.       CONDITIONAL_FIELDS_FIELD_VIEW_HIGHLIGHT => 0,
  1279.       CONDITIONAL_FIELDS_FIELD_VIEW_DESCRIBE => 0,
  1280.     ),
  1281.     'element_view_per_role' => 0,
  1282.     'element_view_roles'    => array(),
  1283.     'element_edit'          => array(
  1284.       CONDITIONAL_FIELDS_FIELD_EDIT_HIDE_ORPHAN => CONDITIONAL_FIELDS_FIELD_EDIT_HIDE_ORPHAN,
  1285.       CONDITIONAL_FIELDS_FIELD_EDIT_HIDE_UNTRIGGERED_ORPHAN => 0,
  1286.       CONDITIONAL_FIELDS_FIELD_EDIT_RESET_UNTRIGGERED => 0,
  1287.     ),
  1288.     'element_edit_per_role' => 0,
  1289.     'element_edit_roles'    => array(),
  1290.     'selector'              => '',
  1291.   );
  1292. }
  1293.  
  1294. /**
  1295.  * Loads all dependencies from the database.
  1296.  *
  1297.  * The result can be filtered by providing an entity type and a bundle name.
  1298.  */
  1299. function conditional_fields_load_dependencies($entity_type = NULL, $bundle = NULL) {
  1300.   // Use the advanced drupal_static() pattern.
  1301.   static $dependencies;
  1302.   if (!isset($dependencies)) {
  1303.     $dependencies = &drupal_static(__FUNCTION__);
  1304.   }
  1305.  
  1306.   if (!$dependencies) {
  1307.     $dependencies = array();
  1308.   }
  1309.  
  1310.   if (!isset($dependencies[$entity_type][$bundle])) {
  1311.     if (!empty($entity_type) && !empty($bundle)) {
  1312.       $dependencies[$entity_type][$bundle] = array();
  1313.     }
  1314.     $default_options = conditional_fields_dependency_default_options();
  1315.  
  1316.     $select = db_select('conditional_fields', 'cf')
  1317.       ->fields('cf', array('id', 'options'))
  1318.       ->orderBy('cf.dependent');
  1319.     $fci_depende = $select->join('field_config_instance', 'fci_dependee', 'cf.dependee = fci_dependee.id');
  1320.     $fci_dependent = $select->join('field_config_instance', 'fci_dependent', 'cf.dependent = fci_dependent.id');
  1321.     $select->addField($fci_depende, 'field_name', 'dependee');
  1322.     $select->addField($fci_dependent, 'field_name', 'dependent');
  1323.     $select->addField($fci_depende, 'entity_type');
  1324.     $select->addField($fci_depende, 'bundle');
  1325.  
  1326.     if ($entity_type) {
  1327.       $select->condition(
  1328.         db_and()
  1329.           ->condition('fci_dependee.entity_type', $entity_type)
  1330.           ->condition('fci_dependent.entity_type', $entity_type)
  1331.       );
  1332.     }
  1333.  
  1334.     if ($bundle) {
  1335.       $select->condition(
  1336.         db_and()
  1337.           ->condition('fci_dependee.bundle', $bundle)
  1338.           ->condition('fci_dependent.bundle', $bundle)
  1339.       );
  1340.     }
  1341.  
  1342.     $result = $select->execute();
  1343.  
  1344.     foreach ($result as $dependency) {
  1345.       $result_entity_type = $entity_type ? $entity_type : $dependency->entity_type;
  1346.       $result_bundle = $bundle ? $bundle : $dependency->bundle;
  1347.  
  1348.       $options = unserialize($dependency->options);
  1349.       $options += $default_options;
  1350.  
  1351.       $dependencies[$result_entity_type][$result_bundle]['dependents'][$dependency->dependent][$dependency->id] = array(
  1352.         'dependee' => $dependency->dependee,
  1353.         'options'  => $options,
  1354.       );
  1355.  
  1356.       $dependencies[$result_entity_type][$result_bundle]['dependees'][$dependency->dependee][$dependency->id] = array(
  1357.         'dependent' => $dependency->dependent,
  1358.         'options'  => $options,
  1359.       );
  1360.     }
  1361.   }
  1362.  
  1363.   if ($entity_type && isset($dependencies[$entity_type])) {
  1364.     if ($bundle && isset($dependencies[$entity_type][$bundle])) {
  1365.       return $dependencies[$entity_type][$bundle];
  1366.     }
  1367.  
  1368.     return $dependencies[$entity_type];
  1369.   }
  1370.  
  1371.   return $dependencies;
  1372. }
  1373.  
  1374. /**
  1375.  * Menu argument loader: loads a dependency from the database.
  1376.  *
  1377.  * @param $id
  1378.  *   The dependency ID.
  1379.  *
  1380.  * @return
  1381.  *   A fully populated dependency array, complete with its conditions, or FALSE
  1382.  *   if the dependency is not found.
  1383.  */
  1384. function conditional_fields_dependency_load($id) {
  1385.   $result = db_select('conditional_fields', 'cf')
  1386.     ->fields('cf', array('id', 'dependee', 'dependent', 'options'))
  1387.     ->condition('id', $id)
  1388.     ->execute()
  1389.     ->fetchAssoc();
  1390.  
  1391.   if (!$result) {
  1392.     return FALSE;
  1393.   }
  1394.  
  1395.   $result['options'] = unserialize($result['options']) + conditional_fields_dependency_default_options();
  1396.  
  1397.   return $result;
  1398. }
  1399.  
  1400. /**
  1401.  * Inserts a new dependency in the database.
  1402.  * For the format of $options,
  1403.  * @see conditional_fields_dependency_default_options()
  1404.  */
  1405. function conditional_fields_dependency_insert($dependee_id, $dependent_id, $options = array()) {
  1406.   $options += conditional_fields_dependency_default_options();
  1407.   $dependency = array(
  1408.     'dependee'  => $dependee_id,
  1409.     'dependent' => $dependent_id,
  1410.     'options'   => $options,
  1411.   );
  1412.  
  1413.   if (drupal_write_record('conditional_fields', $dependency)) {
  1414.     return $dependency['id'];
  1415.   }
  1416. }
  1417.  
  1418. /**
  1419.  * Updates a dependency.
  1420.  */
  1421. function conditional_fields_dependency_update($dependency) {
  1422.   drupal_write_record('conditional_fields', $dependency, 'id');
  1423. }
  1424.  
  1425. /**
  1426.  * Deletes dependencies.
  1427.  */
  1428. function conditional_fields_dependency_delete($dependency_ids) {
  1429.   $or = db_or();
  1430.   foreach ($dependency_ids as $id) {
  1431.     $or = $or->condition('id', $id);
  1432.   }
  1433.  
  1434.   return db_delete('conditional_fields')
  1435.     ->condition($or)
  1436.     ->execute();
  1437. }
  1438.  
  1439. /**
  1440.  * Implements hook_field_delete_instance().
  1441.  *
  1442.  * Delete any dependency associated with the deleted instance.
  1443.  */
  1444. function conditional_fields_field_delete_instance($instance) {
  1445.   db_delete('conditional_fields')
  1446.     ->condition(
  1447.       db_or()
  1448.         ->condition('dependee', $instance['id'])
  1449.         ->condition('dependent', $instance['id']))
  1450.     ->execute();
  1451. }
  1452.  
  1453. /**
  1454.  * Implements hook_theme().
  1455.  */
  1456. function conditional_fields_theme() {
  1457.   return array(
  1458.     'conditional_fields_table' => array(
  1459.       'render element' => 'elements',
  1460.     ),
  1461.   );
  1462. }
  1463.  
  1464. /**
  1465.  * Implements hook_element_info().
  1466.  */
  1467. function conditional_fields_element_info() {
  1468.   return array(
  1469.     'conditional_fields_table' => array(
  1470.       '#theme' => 'conditional_fields_table',
  1471.     ),
  1472.   );
  1473. }
  1474.  
  1475. /**
  1476.  * Implements hook_library().
  1477.  */
  1478. function conditional_fields_library() {
  1479.   return array(
  1480.     'conditional_fields' => array(
  1481.       'tile' => 'Conditional Fields',
  1482.       'version' => '1.0.0',
  1483.       'js' => array(
  1484.         drupal_get_path('module', 'conditional_fields') . '/js/conditional_fields.js' => array('group' => JS_DEFAULT),
  1485.       ),
  1486.       'dependencies' => array(
  1487.         array('system', 'drupal.states'),
  1488.       ),
  1489.     ),
  1490.   );
  1491. }
  1492.  
  1493. /**
  1494.  * Builds a list of supported states that may be applied to a dependent field.
  1495.  */
  1496. function conditional_fields_states() {
  1497.   $states = array(
  1498.     // Supported by States API
  1499.     'visible'    => t('Visible'),
  1500.     '!visible'   => t('Invisible'),
  1501.     '!empty'     => t('Filled with a value'),
  1502.     'empty'      => t('Emptied'),
  1503.     '!disabled'  => t('Enabled'),
  1504.     'disabled'   => t('Disabled'),
  1505.     'checked'    => t('Checked'),
  1506.     '!checked'   => t('Unchecked'),
  1507.     'required'   => t('Required'),
  1508.     '!required'  => t('Optional'),
  1509.     '!collapsed' => t('Expanded'),
  1510.     'collapsed'  => t('Collapsed'),
  1511.     // Supported by Conditional Fields
  1512.     'unchanged'  => t('Unchanged (no state)'),
  1513.     // TODO: Add support to these states:
  1514.     /*
  1515.     'relevant'   => t('Relevant'),
  1516.     '!relevant'  => t('Irrelevant'),
  1517.     'valid'      => t('Valid'),
  1518.     '!valid'     => t('Invalid'),
  1519.     'touched'    => t('Touched'),
  1520.     '!touched'   => t('Untouched'),
  1521.     '!readonly'  => t('Read/Write'),
  1522.     'readonly'   => t('Read Only'),
  1523.     */
  1524.   );
  1525.  
  1526.   // Allow other modules to modify the states
  1527.   drupal_alter('conditional_fields_states', $states);
  1528.  
  1529.   return $states;
  1530. }
  1531.  
  1532. /**
  1533.  * Builds a list of supported effects that may be applied to a dependent field
  1534.  * when it changes from visible to invisible and viceversa. The effects may
  1535.  * have options that will be passed as Javascript settings and used by
  1536.  * conditional_fields.js.
  1537.  *
  1538.  * @return
  1539.  *   An associative array of effects. Each key is an unique name for the effect.
  1540.  *   The value is an associative array:
  1541.  *   - label: The human readable name of the effect.
  1542.  *   - states: The states that can be associated with this effect.
  1543.  *   - options: An associative array of effect options names, field types,
  1544.  *     descriptions and default values.
  1545.  */
  1546. function conditional_fields_effects() {
  1547.   $effects = array(
  1548.     'show' => array(
  1549.       'label' => t('Show/Hide'),
  1550.       'states' => array('visible', '!visible'),
  1551.     ),
  1552.     'fade' => array(
  1553.       'label' => t('Fade in/Fade out'),
  1554.       'states' => array('visible', '!visible'),
  1555.       'options' => array(
  1556.         'speed' => array(
  1557.           '#type' => 'textfield',
  1558.           '#description' => t('The speed at which the animation is performed, in milliseconds.'),
  1559.           '#default_value' => 400,
  1560.         ),
  1561.       ),
  1562.     ),
  1563.     'slide' => array(
  1564.       'label' => t('Slide down/Slide up'),
  1565.       'states' => array('visible', '!visible'),
  1566.       'options' => array(
  1567.         'speed' => array(
  1568.           '#type' => 'textfield',
  1569.           '#description' => t('The speed at which the animation is performed, in milliseconds.'),
  1570.           '#default_value' => 400,
  1571.         ),
  1572.       ),
  1573.     ),
  1574.     'fill' => array(
  1575.       'label' => t('Fill field with a value'),
  1576.       'states' => array('!empty'),
  1577.       'options' => array(
  1578.         'value' => array(
  1579.           '#type' => 'textfield',
  1580.           '#description' => t('The value that should be given to the field when automatically filled.'),
  1581.           '#default_value' => '',
  1582.         ),
  1583.         'reset' => array(
  1584.           '#type' => 'checkbox',
  1585.           '#title' => t('Restore previous value when untriggered'),
  1586.           '#default_value' => 1,
  1587.         ),
  1588.       ),
  1589.     ),
  1590.     'empty' => array(
  1591.       'label' => t('Empty field'),
  1592.       'states' => array('empty'),
  1593.       'options' => array(
  1594.         'value' => array(
  1595.           '#type' => 'hidden',
  1596.           '#description' => t('The value that should be given to the field when automatically emptied.'),
  1597.           '#value' => '',
  1598.           '#default_value' => '',
  1599.         ),
  1600.         'reset' => array(
  1601.           '#type' => 'checkbox',
  1602.           '#title' => t('Restore previous value when untriggered'),
  1603.           '#default_value' => 1,
  1604.         ),
  1605.       ),
  1606.     ),
  1607.   );
  1608.  
  1609.   // Allow other modules to modify the effects.
  1610.   drupal_alter('conditional_fields_effects', $effects);
  1611.  
  1612.   return $effects;
  1613. }
  1614.  
  1615. /**
  1616.  * List of states of a dependee field that may be used to evaluate a condition.
  1617.  */
  1618. function conditional_fields_conditions($checkboxes = TRUE) {
  1619.   // Supported by States API
  1620.   $conditions = array(
  1621.     '!empty'     => t('Filled'),
  1622.     'empty'      => t('Empty'),
  1623.     'touched'    => t('Touched'),
  1624.     '!touched'   => t('Untouched'),
  1625.     'focused'    => t('Focused'),
  1626.     '!focused'   => t('Unfocused'),
  1627.   );
  1628.  
  1629.   if ($checkboxes) {
  1630.     // Relevant only if dependee is a list of checkboxes
  1631.     $conditions['checked'] = t('Checked');
  1632.     $conditions['!checked'] = t('Unchecked');
  1633.   }
  1634.  
  1635.   $conditions['value'] = t('Value');
  1636.  
  1637.   // TODO: Add support from Conditional Fields to these conditions
  1638.   /*
  1639.   '!disabled'  => t('Enabled'),
  1640.   'disabled'   => t('Disabled'),
  1641.   'required'   => t('Required'),
  1642.   '!required'  => t('Optional'),
  1643.   'relevant'   => t('Relevant'),
  1644.   '!relevant'  => t('Irrelevant'),
  1645.   'valid'      => t('Valid'),
  1646.   '!valid'     => t('Invalid'),
  1647.   '!readonly'  => t('Read/Write'),
  1648.   'readonly'   => t('Read Only'),
  1649.   '!collapsed' => t('Expanded'),
  1650.   'collapsed'  => t('Collapsed'),
  1651.   */
  1652.  
  1653.   // Allow other modules to modify the conditions
  1654.   drupal_alter('conditional_fields_conditions', $conditions);
  1655.  
  1656.   return $conditions;
  1657. }
  1658.  
  1659. /**
  1660.  * List of behaviors that can be applied when editing forms and viewing content
  1661.  * with dependencies.
  1662.  */
  1663. function conditional_fields_behaviors($op = NULL) {
  1664.   $behaviors = array(
  1665.     'edit' => array(
  1666.       CONDITIONAL_FIELDS_FIELD_EDIT_HIDE_ORPHAN => t('Hide the dependent if the dependee is not in the form'),
  1667.       CONDITIONAL_FIELDS_FIELD_EDIT_RESET_UNTRIGGERED => t('Reset the dependent to its default values when the form is submitted if the dependency is not triggered.') . '<br /><em>' . t('Note: This setting only applies if the condition is "Value", "Empty", or "Filled" and may not work with some field types. Also, ensure that the default values are valid, since they will not be validated.') . '</em>',
  1668.       // TODO: Implement. Settings are imported from D6 though, they just do nothing for now.
  1669.       /*
  1670.       CONDITIONAL_FIELDS_FIELD_EDIT_HIDE_UNTRIGGERED_ORPHAN => t('Hide the dependent if the dependee is not in the form and the dependency is not triggered.') . '<br /><em>' . t('Note: This setting is not currently not implemented and has no effect.') . '</em>',
  1671.       */
  1672.     ),
  1673.     'view' => array(
  1674.       CONDITIONAL_FIELDS_FIELD_VIEW_EVALUATE => t('Hide the dependent if the dependency is not triggered'),
  1675.       CONDITIONAL_FIELDS_FIELD_VIEW_HIDE_ORPHAN => t('Hide the dependent if the dependee is not viewable by the user'),
  1676.       CONDITIONAL_FIELDS_FIELD_VIEW_HIDE_UNTRIGGERED_ORPHAN => t('Hide the dependent if the dependee is not viewable by the user and the dependency is not triggered'),
  1677.       CONDITIONAL_FIELDS_FIELD_VIEW_HIGHLIGHT => t('Theme the dependent like an error message if the dependency is not triggered'),
  1678.       CONDITIONAL_FIELDS_FIELD_VIEW_DESCRIBE => t('Show a textual description of the dependency under the dependent'),
  1679.     ),
  1680.   );
  1681.  
  1682.   // Allow other modules to modify the options.
  1683.   drupal_alter('conditional_fields_behaviors', $behaviors);
  1684.  
  1685.   if (isset($behaviors[$op])) {
  1686.     return $behaviors[$op];
  1687.   }
  1688.  
  1689.   return $behaviors;
  1690. }
  1691.  
  1692. /**
  1693.  * Builds a list of special fields handlers to be executed when building the
  1694.  * #states array. The keys are handler function names and the key/value pairs
  1695.  * are field properties and their values that trigger the execution of the handler.
  1696.  *
  1697.  * The handlers themselves must accept the parameters $field, $field_info,
  1698.  * $options and $state.
  1699.  *
  1700.  * @see conditional_fields_field_attach_form()
  1701.  */
  1702. function conditional_fields_states_handlers() {
  1703.   $handlers = array(
  1704.     'conditional_fields_states_handler_select_multiple' => array(
  1705.       '#type' => 'select',
  1706.       '#multiple' => TRUE,
  1707.     ),
  1708.     'conditional_fields_states_handler_checkbox' => array(
  1709.       '#type' => 'checkbox',
  1710.     ),
  1711.     'conditional_fields_states_handler_checkboxes' => array(
  1712.       '#type' => 'checkboxes',
  1713.     ),
  1714.     'conditional_fields_states_handler_text' => array(
  1715.       '#type' => 'textfield',
  1716.     ),
  1717.     'conditional_fields_states_handler_textarea' => array(
  1718.       '#type' => 'textarea',
  1719.     ),
  1720.     'conditional_fields_states_handler_date_combo' => array(
  1721.       '#type' => 'date_combo',
  1722.     ),
  1723.     'conditional_fields_states_handler_link_field' => array(
  1724.       '#type' => 'link_field',
  1725.     ),
  1726.     'conditional_fields_states_handler_link_addressfield' => array(
  1727.       '#addressfield' => 1,
  1728.     ),
  1729.   );
  1730.  
  1731.   // Allow other modules to modify the handlers
  1732.   drupal_alter('conditional_fields_states_handlers', $handlers);
  1733.  
  1734.   return $handlers;
  1735. }
  1736.  
  1737. /**
  1738.  * States handler for multiple select lists.
  1739.  *
  1740.  * Multiple select fields always require an array as value.
  1741.  * In addition, since our modified States API triggers a dependency only if all
  1742.  * reference values of type Array are selected, a different selector must be
  1743.  * added for each value of a set for OR, XOR and NOT evaluations.
  1744.  */
  1745. function conditional_fields_states_handler_select_multiple($field, $field_info, $options, &$state) {
  1746.   switch ($options['values_set']) {
  1747.     case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_WIDGET:
  1748.     case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_AND:
  1749.       $state[$options['state']][$options['selector']]['value'] = (array) $state[$options['state']][$options['selector']]['value'];
  1750.       return;
  1751.  
  1752.     case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_XOR:
  1753.       $select_states[$options['state']][] = 'xor';
  1754.  
  1755.     case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_REGEX:
  1756.       $regex = TRUE;
  1757.     case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_NOT:
  1758.     case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_OR:
  1759.       foreach ($options['values'] as $value) {
  1760.         $select_states[$options['state']][] = array(
  1761.           $options['selector'] => array(
  1762.             $options['condition'] => empty($regex) ? array($value) : $options['value'],
  1763.           ),
  1764.         );
  1765.       }
  1766.       break;
  1767.   }
  1768.  
  1769.   $state = $select_states;
  1770. }
  1771.  
  1772. /**
  1773.  * States handler for single on/off checkbox.
  1774.  */
  1775. function conditional_fields_states_handler_checkbox($field, $field_info, $options, &$state) {
  1776.   switch ($options['values_set']) {
  1777.     case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_WIDGET:
  1778.       $checked = $options['value'][0]['value'] == $field['#on_value'] ? TRUE : FALSE;
  1779.       break;
  1780.     case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_REGEX:
  1781.       $checked = preg_match('/' . $options['value']['RegExp'] . '/', $field['#on_value']) ? TRUE : FALSE;
  1782.       break;
  1783.     case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_AND:
  1784.       // ANDing values of a single checkbox doesn't make sense: just use the first value.
  1785.       $checked = $options['values'][0] == $field['#on_value'] ? TRUE : FALSE;
  1786.       break;
  1787.     case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_XOR:
  1788.     case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_OR:
  1789.     case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_NOT:
  1790.       $checked = in_array($field['#on_value'], $options['values']) ? TRUE : FALSE;
  1791.       break;
  1792.   }
  1793.  
  1794.   $state[$options['state']][$options['selector']] = array('checked' => $checked);
  1795. }
  1796.  
  1797. /**
  1798.  * States handler for checkboxes.
  1799.  */
  1800. function conditional_fields_states_handler_checkboxes($field, $field_info, $options, &$state) {
  1801.   // Checkboxes are actually different form fields, so the #states property
  1802.   // has to include a state for each checkbox.
  1803.   $checkboxes_selectors = array();
  1804.  
  1805.   switch ($options['values_set']) {
  1806.     case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_WIDGET:
  1807.       foreach ($options['value'] as $value) {
  1808.         $checkboxes_selectors[conditional_fields_field_selector($field[current($value)])] = array('checked' => TRUE);
  1809.       }
  1810.       break;
  1811.     case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_REGEX:
  1812.       // We interpret this as: checkboxes whose values match the regular
  1813.       // expression should be checked.
  1814.       foreach ($field['#options'] as $key => $label) {
  1815.         if (preg_match('/' . $options['value']['RegExp'] . '/', $key)) {
  1816.           $checkboxes_selectors[conditional_fields_field_selector($field[$key])] = array('checked' => TRUE);
  1817.         }
  1818.       }
  1819.       break;
  1820.     case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_AND:
  1821.       foreach ($options['values'] as $value) {
  1822.         $checkboxes_selectors[conditional_fields_field_selector($field[$value])] = array('checked' => TRUE);
  1823.       }
  1824.       break;
  1825.     case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_XOR:
  1826.       $checkboxes_selectors[] = 'xor';
  1827.     case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_OR:
  1828.     case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_NOT:
  1829.       foreach ($options['values'] as $value) {
  1830.         $checkboxes_selectors[] = array(conditional_fields_field_selector($field[$value]) => array('checked' => TRUE));
  1831.       }
  1832.       break;
  1833.   }
  1834.  
  1835.   $state = array($options['state'] => $checkboxes_selectors);
  1836. }
  1837.  
  1838. /**
  1839.  * States handler for text fields.
  1840.  */
  1841. function conditional_fields_states_handler_text($field, $field_info, $options, &$state) {
  1842.   // Text fields values are keyed by cardinality, so we have to flatten them.
  1843.   // TODO: support multiple values.
  1844.   if ($options['values_set'] == CONDITIONAL_FIELDS_DEPENDENCY_VALUES_WIDGET) {
  1845.     // Cast as array to handle the exception of autocomplete text fields.
  1846.     $value = (array) $state[$options['state']][$options['selector']]['value'][0];
  1847.     $state[$options['state']][$options['selector']]['value'] = array_shift($value);
  1848.   }
  1849. }
  1850.  
  1851. /**
  1852.  * States handler for text areas.
  1853.  */
  1854. function conditional_fields_states_handler_textarea($field, $field_info, $options, &$state) {
  1855.   conditional_fields_states_handler_text($field, $field_info, $options, $state);
  1856. }
  1857.  
  1858. /**
  1859.  * States handler for date combos.
  1860.  */
  1861. function conditional_fields_states_handler_date_combo($field, $field_info, $options, &$state) {
  1862.   // Date text.
  1863.   if ($field_info['instance']['widget']['type'] == 'date_text') {
  1864.     if ($options['values_set'] == CONDITIONAL_FIELDS_DEPENDENCY_VALUES_WIDGET) {
  1865.       $state[$options['state']][$options['selector']]['value'] = $state[$options['state']][$options['selector']]['value'][0]['value']['date'];
  1866.     }
  1867.     return;
  1868.   }
  1869.  
  1870.   // Add a condition for each date part.
  1871.   $date_selectors = array();
  1872.  
  1873.   $regex = $options['values_set'] == CONDITIONAL_FIELDS_DEPENDENCY_VALUES_REGEX;
  1874.  
  1875.   // Date popup.
  1876.   if ($field_info['instance']['widget']['type'] == 'date_popup') {
  1877.     $date_selectors[conditional_fields_field_selector($field['value']['date'])] = array(
  1878.       'value' => $regex ? $options['value'] : $options['value_form'][0]['value']['date'],
  1879.     );
  1880.  
  1881.     if ($field_info['field']['settings']['granularity']['hour'] || $field_info['field']['settings']['granularity']['minute'] || $field_info['field']['settings']['granularity']['second']) {
  1882.       $date_selectors[conditional_fields_field_selector($field['value']['time'])] = array(
  1883.         'value' => $regex ? $options['value'] : $options['value_form'][0]['value']['time'],
  1884.       );
  1885.     }
  1886.   }
  1887.   // Date select.
  1888.   else {
  1889.     foreach ($field_info['field']['settings']['granularity'] as $date_part) {
  1890.       if ($date_part) {
  1891.         $date_selectors[conditional_fields_field_selector($field['value'][$date_part])] = array(
  1892.           'value' => $regex ? $options['value'] : $options['value_form'][0]['value'][$date_part],
  1893.         );
  1894.       }
  1895.     }
  1896.   }
  1897.  
  1898.   $state = array($options['state'] => $date_selectors);
  1899. }
  1900.  
  1901. /**
  1902.  * States handler for links provided by the Link module.
  1903.  */
  1904. function conditional_fields_states_handler_link_field($field, $field_info, $options, &$state) {
  1905.   $link_selectors = array();
  1906.   $regex = $options['values_set'] == CONDITIONAL_FIELDS_DEPENDENCY_VALUES_REGEX;
  1907.  
  1908.   // Add a condition for each link part (Title and URL)
  1909.   if ($field_info['instance']['settings']['title'] == 'optional' || $field_info['instance']['settings']['title'] == 'required') {
  1910.     $link_selectors[conditional_fields_field_selector($field['title'])] = array('value' => $regex ? $options['value'] : $options['value_form'][0]['title']);
  1911.   }
  1912.   $link_selectors[conditional_fields_field_selector($field['url'])] = array('value' => $regex ? $options['value'] : $options['value_form'][0]['url']);
  1913.  
  1914.   $state = array($options['state'] => $link_selectors);
  1915. }
  1916.  
  1917. /**
  1918.  * States handler for links provided by the Addressfield module.
  1919.  */
  1920. function conditional_fields_states_handler_link_addressfield($field, $field_info, $options, &$state) {
  1921.   if ($options['values_set'] != CONDITIONAL_FIELDS_DEPENDENCY_VALUES_WIDGET) {
  1922.     return;
  1923.   }
  1924.  
  1925.   $regex = $options['values_set'] == CONDITIONAL_FIELDS_DEPENDENCY_VALUES_REGEX;
  1926.   $keys = array();
  1927.  
  1928.   if ($field['#handlers']['address']) {
  1929.     $keys += array('country', 'thoroughfare', 'premise', 'postal_code', 'locality', 'administrative_area');
  1930.   }
  1931.  
  1932.   if ($field['#handlers']['organisation']) {
  1933.     $keys += array('organisation_name');
  1934.   }
  1935.  
  1936.   if ($field['#handlers']['name-oneline']) {
  1937.     $keys += array('name_line');
  1938.   }
  1939.   elseif ($field['#handlers']['name-full']) {
  1940.     $keys += array('first_name', 'last_name');
  1941.   }
  1942.  
  1943.   $addressfield_selectors = array();
  1944.  
  1945.   foreach ($keys as $key) {
  1946.     $addressfield_selectors[str_replace('%key', $key, $options['selector'])] = array('value' => $regex ? $options['value'] : $options['value'][0][$key]);
  1947.   }
  1948.  
  1949.   $state = array($options['state'] => $addressfield_selectors);
  1950. }
  1951.  
  1952. /**
  1953.  * Build a textual description of a dependency
  1954.  */
  1955. function conditional_fields_dependency_description($dependee_name, $dependent_name, $options) {
  1956.   $states = conditional_fields_states();
  1957.  
  1958.   if ($options['condition'] == 'value') {
  1959.     $values = implode(', ', $options['values']);
  1960.  
  1961.     switch ($options['values_set']) {
  1962.       case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_WIDGET:
  1963.         if (count($options['value']) == 1) {
  1964.           return t('%dependent_name is !state when %dependee_name has value "@value".', array(
  1965.             '%dependent_name' => $dependent_name,
  1966.             '!state' => drupal_strtolower($states[$options['state']]),
  1967.             '%dependee_name' => $dependee_name,
  1968.             '@value' => current($options['value'][0]),
  1969.           ));
  1970.         }
  1971.         // "Single values" of multiple value fields like checkboxes are not
  1972.         // actually single. Such fields will be ANDed.
  1973.         $value_array = array();
  1974.         foreach ($options['value'] as $value) {
  1975.           $value_array[] = current($value);
  1976.         }
  1977.         $values = implode(', ', $value_array);
  1978.       case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_AND:
  1979.         $description = '%dependent_name is !state when %dependee_name has all the values: @values.';
  1980.         break;
  1981.       case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_OR:
  1982.         $description = '%dependent_name is !state when %dependee_name has at least one of the values: @values.';
  1983.         break;
  1984.       case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_XOR:
  1985.         $description = '%dependent_name is !state when %dependee_name has only one of the values: @values.';
  1986.         break;
  1987.       case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_NOT:
  1988.         $description = '%dependent_name is !state when %dependee_name has none of the values: @values.';
  1989.         break;
  1990.       case CONDITIONAL_FIELDS_DEPENDENCY_VALUES_REGEX:
  1991.         return t('%dependent_name is !state when %dependee_name values match the regular expression: @regex.', array(
  1992.           '%dependent_name' => $dependent_name,
  1993.           '!state' => drupal_strtolower($states[$options['state']]),
  1994.           '%dependee_name' => $dependee_name,
  1995.           '@regex' => $options['value']['RegExp'],
  1996.         ));
  1997.         break;
  1998.     }
  1999.  
  2000.     return t($description, array(
  2001.       '%dependent_name' => $dependent_name,
  2002.       '!state' => drupal_strtolower($states[$options['state']]),
  2003.       '%dependee_name' => $dependee_name,
  2004.       '@values' => $values,
  2005.     ));
  2006.   }
  2007.   else {
  2008.     $conditions = conditional_fields_conditions();
  2009.  
  2010.     return t('%dependent_name is !state when %dependee_name is !condition.', array(
  2011.       '%dependent_name' => $dependent_name,
  2012.       '!state' => drupal_strtolower($states[$options['state']]),
  2013.       '%dependee_name' => $dependee_name,
  2014.       '!condition' => drupal_strtolower($conditions[$options['condition']]),
  2015.     ));
  2016.   }
  2017. }
  2018.  
  2019. /**
  2020.  * Implements hook_module_implements_alter().
  2021.  *
  2022.  * Ensures that some Conditional Fields hooks are processed last.
  2023.  */
  2024. function conditional_fields_module_implements_alter(&$implementations, $hook) {
  2025.   if (isset($implementations['conditional_fields'])) {
  2026.     // The element_info_alter hook should attempt to add its #after_build
  2027.     // callback last to ensure that conditional_fields_form_validate runs after
  2028.     // all other validation routines.
  2029.     // The Features hook must be processed when the fields actually exist.
  2030.     if ($hook == 'element_info_alter' || $hook == 'features_api') {
  2031.       $group = $implementations['conditional_fields'];
  2032.       unset($implementations['conditional_fields']);
  2033.       $implementations['conditional_fields'] = $group;
  2034.     }
  2035.   }
  2036. }
  2037.  
  2038. /**
  2039.  * Implements hook_features_api().
  2040.  */
  2041. function conditional_fields_features_api() {
  2042.   return array(
  2043.     'conditional_fields' => array(
  2044.       'name' => t('Conditional Fields'),
  2045.       'default_hook' => 'conditional_fields_default_fields',
  2046.       'default_file' => FEATURES_DEFAULTS_INCLUDED,
  2047.       'feature_source' => TRUE,
  2048.       'file' => drupal_get_path('module', 'conditional_fields') . '/includes/conditional_fields.features.inc',
  2049.     ),
  2050.   );
  2051. }
  2052.  
  2053. /**
  2054.  * Returns a field's immediate parent bundle.
  2055.  *
  2056.  * @param  array $field A field structure extracted from a form array
  2057.  * @return string|bool  The parent's bundle or FALSE if not found
  2058.  */
  2059. function _conditional_fields_field_parent_bundle($field) {
  2060.   $languages = array_keys(language_list());
  2061.   foreach (array_reverse($field[0]['#field_parents']) as $parent) {
  2062.     if (!is_numeric($parent) && $parent != LANGUAGE_NONE && !in_array($parent, $languages)) {
  2063.       return $parent;
  2064.     }
  2065.   }
  2066.   return FALSE;
  2067. }
Advertisement
Add Comment
Please, Sign In to add comment