diff --git a/conditional_fields.module b/conditional_fields.module index bf1ce0f..26b62ec 100755 --- a/conditional_fields.module +++ b/conditional_fields.module @@ -25,15 +25,14 @@ switch ($path) { break; case 'admin/help#conditional_fields': $output = '

' . t('The Conditional Fields module allows to set fields with allowed values as "controlling fields" for other fields and groups. When a field or group is "controlled", it will only be available for editing and displayed if the selected values of the controlling field match the "trigger values" assigned to it. You can, for example, make a custom "article teaser" field that is shown only if a "Has teaser" checkbox is checked.') . '

'; - $output .= '

' . t('When editing a node, the controlled fields are dynamically shown and hidden with javascript.') . '

'; + $output .= '

' . t('When editing a node, the controlled fields are dynamically shown and hidden with Javascript.') . '

'; $output .= '

' . t('On node view, the controlled fields which were left untriggered are hidden.') . '

'; $output .= '

' . t('Once the module is activated, a new set of options will appear in the editing form of cck fields, from where you can select which of the allowed values available of candidate "controlling" fields will make the field "controlled". If - Not controlling - or no value is selected, the field will be shown as usual.') . '

'; $output .= '

' . t('These are the requisites to make a field controllable:') . '

'; $output .= ''; $output .= '

' . t('There is also a "Conditional fields" settings tab in every content type admin page.') . '

'; $output .= '

' . t('The Conditional Fields Handbook contains further explanations and examples.', array('@handbook' => url('http://drupal.org/node/475488'))) . '

'; @@ -180,7 +179,7 @@ function _conditional_fields_admin_submit($form, &$form_state) { if ($form_state['values']['delete'] == 1) { conditional_fields_node_type_delete($type); - drupal_set_message(t('All configured conditional fields for this content type have been deleted.')); + drupal_set_message(t('All configured dependencies for this content type have been deleted.')); } } @@ -295,12 +294,12 @@ function conditional_fields_form_alter(&$form, $form_state, $form_id) { $form['#submit'][] = 'conditional_fields_fieldgroup_remove_group_submit'; break; case 'content_add_more_js': - // Handle ahah multiple fields + // Handle ahah multiple fields. foreach ($form as $item_name => $item) { if (db_result(db_query("SELECT COUNT(*) FROM {conditional_fields} WHERE field_name = '%s'", $item_name))) { $form[$item_name]['#post_render'] = array_merge(array('conditional_fields_add_more_post_render'), (array)$form[$item_name]['#post_render']); foreach (element_children($form[$item_name]) as $element) { - conditional_fields_custom_required_field($form[$item_name][$element], $form['#field_info'][$item_name]); + conditional_fields_custom_required_field($form, $form[$element], $form['#field_info'][$required_field_name]); } } } @@ -622,14 +621,18 @@ function conditional_fields_allowed_values($field) { } /** - * Alter node form. We do it in after_build for compatibility - * with non-core CCK widgets + * Alters node form. + * + * We do the actual processing in after_build where the fields will be fully + * built. */ function conditional_fields_node_form(&$form, $form_state) { $form['#after_build'][] = 'conditional_fields_node_after_build'; } /** + * Node form after build callback. + * * Main tasks: * - Create javascript settings * - Prepare custom validation for required controlled fields @@ -649,114 +652,109 @@ function conditional_fields_node_after_build($form, &$form_state) { return $form; } - $controlling_fields = array(); - $missing_controlling_fields = array(); - $controlled_fields = array(); - $required_fields = array(); - $js_settings = array(); - foreach ($data as $row) { $controlling_fields[$row['control_field_name']][$row['field_name']] = $row['trigger_values']; $controlled_fields[$row['field_name']][$row['control_field_name']] = $row['trigger_values']; + + // Store the parent groups of the field within the form for later use. + if (!isset($field_parents[$row['control_field_name']])) { + $field_parents[$row['control_field_name']] = conditional_fields_field_get_parents($form, $row['control_field_name']); + } + + if (!isset($field_parents[$row['field_name']])) { + $field_parents[$row['field_name']] = conditional_fields_field_get_parents($form, $row['field_name']); + } } /* Handle controlling fields */ - foreach ($controlling_fields as $controlling_field_name => $controlling_field_descendants) { + $controlling_fields_items = $missing_controlling_fields = array(); + + foreach (array_keys($controlling_fields) as $controlling_field_name) { // Check if the controlling field is in the form, user has access to it, and is editable. - $group_of_controlling_field = conditional_fields_get_group($type_name, $controlling_field_name); - $controlling_field = conditional_fields_item_in_form($form, $controlling_field_name, $group_of_controlling_field); + $controlling_field = conditional_fields_item_in_form($form, $field_parents[$controlling_field_name]); + $controlling_fields_items[$controlling_field_name] = &$controlling_field; + if (!$controlling_field || $controlling_field['#access'] === FALSE || $controlling_field['#type'] == 'markup') { $missing_controlling_fields[] = $controlling_field_name; continue; } // Set values on form for themeing. - if ($group_of_controlling_field) { - $form[$group_of_controlling_field][$controlling_field_name]['#controlling_fields'] = TRUE; - conditional_fields_item_apply_theme($form[$group_of_controlling_field][$controlling_field_name]); - } - else { - $form[$controlling_field_name]['#controlling_fields'] = TRUE; - conditional_fields_item_apply_theme($form[$controlling_field_name]); - } + conditional_fields_field_apply_theme($form, $controlling_field, '#controlling_fields'); } /* Handle controlled fields */ - foreach ($controlled_fields as $controlled_field_name => $controlled_field_parents) { + $required_fields = $js_settings = array(); + + foreach ($controlled_fields as $controlled_field_name => $controlled_field_controlling) { // Check if the controlled field is in the form and user has access to it. - $group_of_controlled_field = conditional_fields_get_group($type_name, $controlled_field_name); - $controlled_field = conditional_fields_item_in_form($form, $controlled_field_name, $group_of_controlled_field); + $controlled_field = conditional_fields_item_in_form($form, $field_parents[$controlled_field_name]); + if (!$controlled_field || (isset($controlled_field['#access']) && $controlled_field['#access'] === FALSE)) { continue; } // Handle orphaned fields. - foreach ($missing_controlling_fields as $missing_controlling_field) { - unset($controlled_field_parents[$missing_controlling_field]); - } - if (count($controlled_field_parents) == 0) { + $orphaned = array_intersect($missing_controlling_fields, array_keys($controlled_field_controlling)) == array_keys($controlled_field_controlling); + if ($orphaned) { $orphaned_settings = variable_get('c_fields_edit_' . $type_name, C_FIELDS_ORPHANED_SHOW_TRIGGERED); + switch ($orphaned_settings) { case C_FIELDS_ORPHANED_SHOW_TRIGGERED: - // Show only triggered fields. E.g.: fields whose controlling - // fields have triggering values set by default, or set by - // another user with permissions. + // Show only triggered fields, that is fields whose controlling fields + // have triggering values set by default, or set by another user with + // permissions. $triggered = TRUE; - foreach ($controlling_fields as $controlling_field_name => $controlling_field_descendants) { - if ($controlling_field_descendants[$controlled_field_name]) { - if (!conditional_fields_is_triggered($form_state['values'][$controlling_field_name], $controlling_field_descendants[$controlled_field_name])) { - $triggered = FALSE; - break; - } + foreach ($controlled_field_controlling as $controlled_field_controlling_name => $trigger_values) { + if (!conditional_fields_is_triggered($form_state['values'][$controlled_field_controlling_name], $trigger_values)) { + $triggered = FALSE; + break; } } + + // If the field was triggered, do nothing; if it wasn't triggered, + // apply the C_FIELDS_ORPHANED_HIDE behavior. if ($triggered) { break; } + case C_FIELDS_ORPHANED_HIDE: - // Unset controlled field. - if ($group_of_controlled_field) { - unset($form[$group_of_controlled_field][$controlled_field_name]); - } - else { - unset($form[$controlled_field_name]); - } + // Remove access to controlled field. + $controlled_field['#access'] = FALSE; + + // Save the changed field back into the form. + conditional_fields_array_set_nested_value($form, $field_parents[$controlled_field_name], $controlled_field); break; + case C_FIELDS_ORPHANED_SHOW_ALL: // Do nothing: the default behavior is ok. } continue; } - if ($controlled_field['#required']) { - $required_fields[$controlled_field_name] = array('field' => $controlled_field_name, 'in_group' => $group_of_controlled_field); - } - $is_group = (strpos($controlled_field_name , 'group_') === 0 ? TRUE : FALSE); - - // Required fields inside a controlled fieldgroup - // must be handled by conditional fields. + // Set values on form for themeing. + $is_group = strpos($controlled_field_name , 'group_') === 0 ? TRUE : FALSE; if ($is_group) { - foreach (element_children($controlled_field) as $group_element) { - if ($controlled_field[$group_element]['#required'] && !$controlled_fields[$group_element]) { - $required_fields[$group_element] = array('field' => $group_element, 'in_group' => $controlled_field_name); - } - } + conditional_fields_fieldgroup_apply_theme($form, $controlled_field); + } + else { + conditional_fields_field_apply_theme($form, $controlled_field, '#controlled_fields'); } - // Set values on form for themeing. - if ($group_of_controlled_field) { - $form[$group_of_controlled_field][$controlled_field_name]['#controlled_fields'] = TRUE; - conditional_fields_item_apply_theme($form[$group_of_controlled_field][$controlled_field_name]); + // Build an array of required fields for later use. + if ($controlled_field['#required']) { + $required_fields[$controlled_field_name] = $controlled_field; } - else { - $form[$controlled_field_name]['#controlled_fields'] = TRUE; - $is_group ? conditional_fields_item_apply_theme($form[$controlled_field_name], $controlled_field_name) : conditional_fields_item_apply_theme($form[$controlled_field_name]); + // Required fields inside a controlled fieldgroup must be handled even if + // they are not conditional fields themeslves. + if ($is_group) { + _conditional_fields_find_required_fields_recurse($form, $controlled_field, $controlled_fields, $required_fields, $field_parents); } - // Add fields to javascript settings - // TODO: Use unique ids (requires per-widget settings) + // Add fields to javascript settings. + // TODO: Use unique ids (requires per-widget settings). $js_controlled_field_id = '#conditional-' . conditional_fields_form_clean_id($controlled_field_name); - foreach ($controlled_field_parents as $controlling_field_name => $trigger_values) { + foreach ($controlled_field_controlling as $controlling_field_name => $trigger_values) { $js_controlling_field_id = '#conditional-' . conditional_fields_form_clean_id($controlling_field_name); $js_settings['controlling_fields'][$js_controlling_field_id][$js_controlled_field_id] = array('field_id' => $js_controlled_field_id, 'trigger_values' => $trigger_values); } @@ -765,15 +763,9 @@ function conditional_fields_node_after_build($form, &$form_state) { // Controlled fields should only be required when triggered. // Since required fields validation is hardcoded in _form_validate, // we need to unset the #required property and perform a custom validation. - foreach ($required_fields as $field) { - if ($field['in_group']) { - conditional_fields_custom_required_field($form[$field['in_group']][$field['field']], $form['#field_info'][$field['field']]); - conditional_fields_item_apply_theme($form[$field['in_group']][$field['field']]); - } - else { - conditional_fields_custom_required_field($form[$field['field']], $form['#field_info'][$field['field']]); - conditional_fields_item_apply_theme($form[$field['field']]); - } + foreach ($required_fields as $required_field_name => $required_field) { + conditional_fields_custom_required_field($form, $required_field, $form['#field_info'][$required_field_name]); + conditional_fields_field_apply_theme($form, $required_field); } // Apply user interface settings @@ -792,72 +784,106 @@ function conditional_fields_node_after_build($form, &$form_state) { conditional_fields_add_js($js_settings); } - // Pass variables for validation - $form['#conditional_fields']['data'] = $data; - $form['#conditional_fields']['required_fields'] = $required_fields; - $form['#conditional_fields']['settings'] = $js_settings; + // Pass variables for validation. + $form['#conditional_fields'] = array( + 'data' => $data, + 'required_fields' => $required_fields, + 'settings' => $js_settings, + ); - // Add validation function + // Add validation function. $form['#validate'] = array_merge(array('conditional_fields_node_form_validate'), (array)$form['#validate']); return $form; } /** - * Insert appropriate themeing functions in a conditional field form element. + * Validation for node editing form. */ -function conditional_fields_item_apply_theme(&$element, $group_name = '') { - if (!empty($group_name)) { - $element['#group_id'] = 'conditional-' . conditional_fields_form_clean_id($group_name); - // We add themeing in post_render so the wrapping is outside the fieldset. - $post_render = (isset($element['#post_render']) && is_array($element['#post_render'])) ? $element['#post_render'] : array(); - $element['#post_render'] = array_merge(array('conditional_fields_fieldgroup_post_render'), $post_render); - } - else { - // Save a previously set function so it can be called before rendering the field - if (!empty($element['#theme']) && $element['#theme'] != 'conditional_fields_form_item') { - $element['#conditional_fields_theme'] = $element['#theme']; - } - $element['#theme'] = 'conditional_fields_form_item'; +function conditional_fields_node_form_validate($form, &$form_state) { + // When form fails validation, hook_form_alter is not called, so we add js here too. + if ($form['#conditional_fields']['settings']['ui_settings']) { + conditional_fields_add_js($form['#conditional_fields']['settings']); } -} -/** - * Find an item in a form by key. If it's a CCK field, the function - * will find it using field_info. - */ -function conditional_fields_item_in_form($form, $item_name, $group = FALSE) { - static $items; - if (!empty($items[$item_name])) { - return $items[$item_name]; + // Rebuild the array grouping the data by controlled fields. + // Needed to handle multiple controlling field per controlled field. + $controlled_fields = array(); + foreach ($form['#conditional_fields']['data'] as $row) { + $controlled_fields[$row['field_name']][$row['control_field_name']] = $row['trigger_values']; } - if ($group) { - if (!empty($form[$group][$item_name])) { - $items[$item_name] = $form[$group][$item_name]; - } - elseif (!empty($form[$group][$form['#field_info'][$item_name]['display_settings']['parent']][$item_name])) { - $items[$item_name] = $form[$group][$form['#field_info'][$item_name]['display_settings']['parent']][$item_name]; + $reset_untriggered = variable_get('c_fields_reset_default_' . $form['type']['#value'], 1) && !in_array('node_form_build_preview', (array) $form_state['submit_handlers']); + + // Build an array of triggered dependencies. + $triggered = array(); + foreach ($controlled_fields as $controlled_field_name => $controlling_fields) { + // Check if all controlling field were triggered. + foreach ($controlling_fields as $controlling_field_name => $trigger_values) { + $triggered[$controlled_field_name] = conditional_fields_is_triggered($form_state['values'][$controlling_field_name], $trigger_values); + + if (!$triggered[$controlled_field_name]) { + // Do not submit values of controlled fields which were not triggered (except on preview). + if ($reset_untriggered) { + // We are checking a controlled field. + if (isset($form['#conditional_fields']['required_fields'][$controlled_field_name])) { + conditional_fields_reset_to_default_value($form, $form_state, $controlled_field_name); + } + // We are checking a controlled group, so reset to their default values + // all its descendant fields. + else { + $controlled_field_parents = conditional_fields_field_get_parents($form, $controlled_field_name); + $controlled_field = conditional_fields_item_in_form($form, $controlled_field_parents); + foreach (conditional_fields_find_descendant_fields_recurse($form, $controlled_field) as $descendant) { + conditional_fields_reset_to_default_value($form, $form_state, $descendant['#field_name']); + } + } + } + + break; + } } } - else { - if (!empty($form[$item_name])) { - $items[$item_name] = $form[$item_name]; + + // Check conditionally required fields. + foreach ($form['#conditional_fields']['required_fields'] as $required_field_name => $required_field) { + // We are checking a controlled field. + if (isset($controlled_fields[$required_field_name])) { + // Check if the controlled field is triggered and empty. + $fields_to_check = array($required_field_name); } - elseif (!empty($form[$form['#field_info'][$item_name]['display_settings']['parent']][$item_name])) { - $items[$item_name] = $form[$form['#field_info'][$item_name]['display_settings']['parent']][$item_name]; + // We are checking a field inside a controlled group, so require it only if + // any dependency of an ancestor group was triggered. + else { + $fields_to_check = $required_field['#array_parents']; + } + + foreach ($fields_to_check as $field) { + if ($triggered[$field] && conditional_fields_is_empty($form_state['values'][$field], $required_field, $form['#field_info'][$field])) { + form_error($required_field, t('!name field is required.', array('!name' => $required_field['#title']))); + break; + } } } +} + +/** + * Retrieves an item from a form, given its parents. + */ +function conditional_fields_item_in_form(&$form, $field_parents) { + static $items; + $field_name = end($field_parents); - if (!empty($items[$item_name])) { - return $items[$item_name]; + if (!isset($items[$field_name])) { + // Retrieve the item from the form using an array of parents. + $items[$field_name] = conditional_fields_array_get_nested_value($form, $field_parents); } - return FALSE; + return $items[$field_name]; } /** - * Get the fieldgroup of a field + * Returns the fieldgroup of a field. */ function conditional_fields_get_group($type_name, $field_name) { if (!module_exists('fieldgroup')) { @@ -867,130 +893,242 @@ function conditional_fields_get_group($type_name, $field_name) { } /** - * Validation for node editing form. + * Return an array of possible parent groups for a field within a node form. + * + * Wraps around fieldgroup_get_parents since it only exists in CCK 3. + * + * @return + * An array of field parents. Note that the function doesn't check if the + * field is actually located at this position in the form structure. */ -function conditional_fields_node_form_validate($form, &$form_state) { - // When form fails validation, hook_form_alter is not called, so we add js here too - if ($form['#conditional_fields']['settings']['ui_settings']) { - conditional_fields_add_js($form['#conditional_fields']['settings']); +function conditional_fields_field_get_parents($form, $field_name) { + $content_type = $form['type']['#value']; + + // CCK 3 fieldgroups. + if (function_exists('fieldgroup_get_parents')) { + $field_parents = (array) fieldgroup_get_parents($content_type, $field_name); + // Reverse the group tree, since we need it from top to bottom. + $field_parents = array_reverse($field_parents); + } + // CCK 2 fieldgroups. + else { + if (strpos($field_name, 'group_') === 0) { + $field_parents = array($field_name); + } + else { + $group = conditional_fields_get_group($type_name, $field_name); + if ($group) { + $field_parents = array($group); + } + } } - // Rebuild the array grouping the data by controlled fields - // Needed to handle multiple controlling field per controlled field - $controlled_fields = array(); - foreach ($form['#conditional_fields']['data'] as $row) { - $controlled_fields[$row['field_name']][$row['control_field_name']] = $row['trigger_values']; + // Add the field itself to the parents array, but not if it's a fieldgroup, + // since it would be already there. + if (strpos($field_name, 'field_') === 0) { + $field_parents[] = $field_name; } - foreach ($controlled_fields as $controlled_field_name => $controlling_fields) { - // Check if all controlling field were triggered - $triggered = FALSE; - foreach ($controlling_fields as $controlling_field_name => $trigger_values) { - $triggered = conditional_fields_is_triggered($form_state['values'][$controlling_field_name], $trigger_values); - if ($triggered == FALSE) { - break; + return $field_parents; +} + +/** + * Swaps a field's theme callback with a conditional fields wrapper. + * + * @param $property + * A custom property used when themeing to distinguish the field type. + * Either empty, "#controlling_fields" or "#controlled_fields". + * It is empty when applying theme to required fields into controlled groups, + * that are not conditional fields themselves. + */ +function conditional_fields_field_apply_theme(&$form, &$field, $property = '') { + // Save any previously set callback so it can be called before rendering the field. + if (isset($field['#theme']) && $field['#theme'] != 'conditional_fields_form_item') { + $field['#conditional_fields_theme'] = $field['#theme']; + } + $field['#theme'] = 'conditional_fields_form_item'; + + // Set the custom property. + if ($property) { + $field[$property] = TRUE; + } + + // Save the changed field back into the form. + conditional_fields_array_set_nested_value($form, (array) $field['#array_parents'], $field); +} + +/** + * Adds some needed properties to a controlled fieldgroup form element. + * + * @param $property + * A custom property used when themeing to distinguish the type of field. + * Either "#controlling_fields" or "#controlled_fields". + */ +function conditional_fields_fieldgroup_apply_theme(&$form, &$group) { + $group_name = end($group['#array_parents']); + $group['#group_id'] = 'conditional-' . conditional_fields_form_clean_id($group_name); + + // We add themeing in post_render so the wrapping is outside the fieldset. + $post_render = (isset($group['#post_render']) && is_array($group['#post_render'])) ? $group['#post_render'] : array(); + $group['#post_render'] = array_merge(array('conditional_fields_fieldgroup_post_render'), $post_render); + + // Save the changed field back into the form. + conditional_fields_array_set_nested_value($form, $group['#array_parents'], $group); +} + +/** + * Helper function for conditional_fields_node_after_build; finds all required + * fields within $element and adds some needed information to the + * $required_fields and $field_parents arrays. + */ +function _conditional_fields_find_required_fields_recurse($form, $element, $controlled_fields, &$required_fields, &$field_parents) { + foreach (element_children($element) as $child) { + if (strpos($child , 'field_') === 0) { + if ($element[$child]['#required'] && !isset($controlled_fields[$child])) { + $required_fields[$child] = $element[$child]; + $field_parents[$child] = array_merge($element['#array_parents'], array($child)); } } + elseif (strpos($child , 'group_') === 0) { + _conditional_fields_find_required_fields_recurse($form, $element[$child], $controlled_fields, $required_fields, $field_parents); + } + } +} - $required_fields = $form['#conditional_fields']['required_fields']; +/** + * Returns an array of all fields that are descendants of the fieldgroup + * $element within $form. + */ +function conditional_fields_find_descendant_fields_recurse($form, $element, $descendant_fields = array()) { + foreach (element_children($element) as $child) { + if (strpos($child , 'field_') === 0) { + $descendant_fields[] = $element[$child]; + } + elseif (strpos($child , 'group_') === 0) { + conditional_fields_find_descendant_fields_recurse($form, $element[$child], $descendant_fields); + } + } - if (!empty($required_fields[$controlled_field_name]['in_group'])) { - $controlled_field = &$form[$required_fields[$controlled_field_name]['in_group']][$controlled_field_name]; + return $descendant_fields; +} + +/** + * Retrieves a value from a nested array with variable depth. + * + * This function is a straight backport of drupal_array_get_nested_value(). + * + * @param $array + * The array from which to get the value. + * @param $parents + * An array of parent keys of the value, starting with the outermost key. + * @param $key_exists + * (optional) If given, an already defined variable that is altered by + * reference. + * + * @return + * The requested nested value. Possibly NULL if the value is NULL or not all + * nested parent keys exist. $key_exists is altered by reference and is a + * Boolean that indicates whether all nested parent keys exist (TRUE) or not + * (FALSE). This allows to distinguish between the two possibilities when NULL + * is returned. + * + * @see drupal_array_get_nested_value() + */ +function conditional_fields_array_get_nested_value(array &$array, array $parents, &$key_exists = NULL) { + $ref = &$array; + foreach ($parents as $parent) { + if (is_array($ref) && array_key_exists($parent, $ref)) { + $ref = &$ref[$parent]; } else { - $controlled_field = &$form[$controlled_field_name]; + $key_exists = FALSE; + return NULL; } + } + $key_exists = TRUE; + return $ref; +} - // Controlled field - if (strpos($controlled_field_name , 'field_') === 0) { - if ($triggered) { - // Check required - if (!empty($required_fields) && $required_fields[$controlled_field_name]) { - $in_group = $required_fields[$controlled_field_name]['in_group']; - // Check if the controlled field is empty - if (conditional_fields_is_empty($form_state['values'][$controlled_field_name], $in_group ? $form[$in_group][$controlled_field_name] : $form[$controlled_field_name], $form['#field_info'][$controlled_field_name])) { - // Check whether the controlled field is in a group or not and set error accordingly - if (!$in_group) { - form_error($controlled_field, t('!name field is required.', array('!name' => $controlled_field['#title']))); - } - else { - // Set error only if the containing group is not controlled or is controlled and triggered - $set_error = TRUE; - foreach ($form['#conditional_fields']['data'] as $row_check_containing_group) { - if ($row_check_containing_group['field_name'] == $in_group) { - if (!conditional_fields_is_triggered($form_state['values'][$row_check_containing_group['control_field_name']], $row_check_containing_group['trigger_values'])) { - $set_error = FALSE; - } - break; - } - } - if ($set_error) { - form_error($controlled_field, t('!name field is required.', array('!name' => $controlled_field['#title']))); - } - } - } - } - } - else { - // Do not submit values of controlled fields which were not triggered (except on preview) - if (variable_get('c_fields_reset_default_' . $form['type']['#value'], 1) - && !in_array('node_form_build_preview', (array)$form_state['submit_handlers'])) { - // Load default values like in content_field_form() in content.node_form.inc - $controlled_field_info = $form['#field_info'][$controlled_field_name]; - if (content_callback('widget', 'default value', $controlled_field_info) != CONTENT_CALLBACK_NONE) { - $callback = content_callback('widget', 'default value', $controlled_field_info) == CONTENT_CALLBACK_CUSTOM ? $controlled_field_info['widget']['module'] .'_default_value' : 'content_default_value'; - if (function_exists($callback)) { - $items = $callback($form, $form_state, $controlled_field_info, 0); - } - } - $form_state['values'][$controlled_field_name] = $items; - } - } +/** + * Sets a value in a nested array with variable depth. + * + * This function is a straight backport of drupal_array_set_nested_value(). + * + * @param $array + * A reference to the array to modify. + * @param $parents + * An array of parent keys, starting with the outermost key. + * @param $value + * The value to set. + * @param $force + * (Optional) If TRUE, the value is forced into the structure even if it + * requires the deletion of an already existing non-array parent value. If + * FALSE, PHP throws an error if trying to add into a value that is not an + * array. Defaults to FALSE. + * + * @see drupal_array_set_nested_value() + */ +function conditional_fields_array_set_nested_value(array &$array, array $parents, $value, $force = FALSE) { + $ref = &$array; + foreach ($parents as $parent) { + // PHP auto-creates container arrays and NULL entries without error if $ref + // is NULL, but throws an error if $ref is set, but not an array. + if ($force && isset($ref) && !is_array($ref)) { + $ref = array(); } - // Controlled group - elseif (strpos($controlled_field_name, 'group_') === 0) { - foreach (element_children($controlled_field) as $field_in_group) { - // Check if the controlling field was triggered - if ($triggered) { - // Check required - if (!empty($required_fields) - && $required_fields[$field_in_group] - && !$controlled_field[$field_in_group]['#controlled_fields'] - && conditional_fields_is_empty($form_state['values'][$field_in_group], $form[$controlled_field_name][$field_in_group], $form['#field_info'][$field_in_group])) { - form_error($controlled_field[$field_in_group], - t('!name field is required.', - array('!name' => $controlled_field[$field_in_group]['#title']))); - } - } - else { - // Do not submit values of controlled fields which were not triggered (except on preview) - if (variable_get('c_fields_reset_default_' . $form['type']['#value'], 1) && - !isset($form_state['node_preview'])) { - // Load default values like in content_field_form() in content.node_form.inc - $field_in_group_info = $form['#field_info'][$field_in_group]; - if (content_callback('widget', 'default value', $field_in_group_info) != CONTENT_CALLBACK_NONE) { - $callback = content_callback('widget', 'default value', $field_in_group_info) == CONTENT_CALLBACK_CUSTOM ? $field['widget']['module'] .'_default_value' : 'content_default_value'; - if (function_exists($callback)) { - $items = $callback($form, $form_state, $field_in_group_info, 0); - } - } - $form_state['values'][$field_in_group] = $items; - } - } + $ref = &$ref[$parent]; + } + $ref = $value; +} + +/** + * Resets a field to its default value during form validation or submission. + */ +function conditional_fields_reset_to_default_value($form, &$form_state, $field_name) { + // Load default values like in content_field_form() in content.node_form.inc. + $field_info = $form['#field_info'][$field_name]; + if (content_callback('widget', 'default value', $field_info) != CONTENT_CALLBACK_NONE) { + $callback = content_callback('widget', 'default value', $field_info) == CONTENT_CALLBACK_CUSTOM ? $field_info['widget']['module'] .'_default_value' : 'content_default_value'; + if (function_exists($callback)) { + $items = $callback($form, $form_state, $field_info, 0); + } + } + $form_state['values'][$field_name] = $items; +} + +/** + * Returns TRUE if a dependency was triggered. + * + * @param $selected_values + * The values of the controlling field selected by the user when creating the node. + * @param $trigger_values + * An array containing the information we need to select the trigger values. + */ +function conditional_fields_is_triggered($selected_values, $trigger_values) { + foreach ((array)$selected_values as $values) { + foreach ((array)$values as $value) { + if (isset($value) && in_array($value, $trigger_values)) { + return TRUE; } } } + return FALSE; } /** * Checks if a submitted field value is empty. */ function conditional_fields_is_empty($item, $field, $field_info) { - // First, check if the module that provides the field implements + // First, apply some module specific logic. + if ($field_info['module'] == 'emvideo') { + return empty($field[0]['embed']); + } + + // Second, check if the module that provides the field implements // hook_content_is_empty, and, if so, use it. if (in_array($field_info['module'], module_implements('content_is_empty'))) { $empty = $field_info['module'] . '_content_is_empty'; - return $empty($item, $field); + return $empty($item[0], $field); } $value = NULL; @@ -1019,22 +1157,6 @@ function conditional_fields_is_empty($item, $field, $field_info) { } /** - * Returns true if the field was triggered - * $selected_values The values of the controlling field selected by the user when creating the node - * $trigger_values An array containing the information we need to select the trigger values - */ -function conditional_fields_is_triggered($selected_values, $trigger_values) { - foreach ((array)$selected_values as $values) { - foreach ((array)$values as $value) { - if (isset($value) && in_array($value, $trigger_values)) { - return TRUE; - } - } - } - return FALSE; -} - -/** * Returns an array of conditional fields settings for a given node type. * $structure can be either 'flat' or 'row' . 'row' data is data per row, * while 'flat' data is a list of both controlling and controlled fields. @@ -1301,31 +1423,11 @@ function conditional_fields_delete_field($type_name, $controlled_field, $control } /** - * Wrapper for conditional_fields_set_required_field_recurse - */ -function conditional_fields_set_required_field($item) { - conditional_fields_set_required_field_recurse($item); - return $item; -}; - -/** - * Recursive function to set required for all conditionally required fields. - * This causes Drupal to render conditionally required fields in a way that - * indicates they are required when visible. - */ -function conditional_fields_set_required_field_recurse(&$item) { - $item['#required'] = TRUE; - foreach (element_children($item) as $child) { - conditional_fields_set_required_field_recurse($item[$child]); - } -} - -/** * Unset the #required property and set a #conditional_fields_required property for custom validation. */ -function conditional_fields_custom_required_field(&$field, &$field_info = NULL) { +function conditional_fields_custom_required_field(&$form, &$field, &$field_info = NULL) { if (isset($field['#required']) && $field['#required']) { - unset($field['#required']); + $field['#required'] = FALSE; $field['#conditional_fields_required'] = TRUE; // Some modules like FileField use the field info property to check if the @@ -1338,16 +1440,18 @@ function conditional_fields_custom_required_field(&$field, &$field_info = NULL) // otherwise _form_validate will think that an invalid choice is selected // when the field is submitted with no value. if ($field['#type'] == 'radios' && !$field['#default_value'] && isset($field['#needs_validation']) && $field['#needs_validation']) { - unset($field['#needs_validation']); - $field['#element_validate'] = array_merge(array('conditional_fields_required_radios_validate'), (array) $field['#validate']); + $field['#element_validate'] = array_merge(array('conditional_fields_required_radios_validate'), (array) $field['#element_validate']); + $field['#needs_validation'] = FALSE; } + + conditional_fields_array_set_nested_value($form, $field['#array_parents'], $field); } + foreach (element_children($field) as $child) { - conditional_fields_custom_required_field($field[$child]); + conditional_fields_custom_required_field($form, $field[$child]); } } - /** * Custom validation for radio buttons. * Reproduces the behavior of _form_validate, while adding an empty option. @@ -1372,6 +1476,26 @@ function conditional_fields_required_radios_validate($elements) { } /** + * Wrapper for conditional_fields_set_required_field_recurse + */ +function conditional_fields_set_required_field($item) { + conditional_fields_set_required_field_recurse($item); + return $item; +}; + +/** + * Recursive function to set required for all conditionally required fields. + * This causes Drupal to render conditionally required fields in a way that + * indicates they are required when visible. + */ +function conditional_fields_set_required_field_recurse(&$item) { + $item['#required'] = TRUE; + foreach (element_children($item) as $child) { + conditional_fields_set_required_field_recurse($item[$child]); + } +} + +/** * Implementation of hook_features_api() (features module). */ function conditional_fields_features_api() {