summaryrefslogtreecommitdiff
path: root/modules/field/modules/options/options.module
diff options
context:
space:
mode:
Diffstat (limited to 'modules/field/modules/options/options.module')
-rw-r--r--modules/field/modules/options/options.module438
1 files changed, 132 insertions, 306 deletions
diff --git a/modules/field/modules/options/options.module b/modules/field/modules/options/options.module
index 22099fd9e..a05ed375b 100644
--- a/modules/field/modules/options/options.module
+++ b/modules/field/modules/options/options.module
@@ -11,36 +11,16 @@
*/
function options_theme() {
return array(
- 'options_select' => array(
- 'render element' => 'element',
- ),
- 'options_buttons' => array(
- 'render element' => 'element',
- ),
- 'options_onoff' => array(
- 'render element' => 'element',
- ),
'options_none' => array(
'variables' => array('instance' => NULL),
- ),
+ ),
);
}
/**
* Implement hook_field_widget_info().
- *
- * We need custom handling of multiple values because we need
- * to combine them into a options list rather than display
- * cardinality elements. We will use the field module's default
- * handling for default values.
- *
- * Callbacks can be omitted if default handing is used.
- * They're included here just so this module can be used
- * as an example for custom modules that might do things
- * differently.
*/
function options_field_widget_info() {
-
return array(
'options_select' => array(
'label' => t('Select list'),
@@ -67,290 +47,180 @@ function options_field_widget_info() {
}
/**
- * Implement hook_element_info().
- */
-function options_element_info() {
- $types['options_select'] = array(
- '#input' => TRUE,
- '#columns' => array('value'),
- '#delta' => 0,
- '#process' => array('options_select_elements_process'),
- );
- $types['options_buttons'] = array(
- '#input' => TRUE,
- '#columns' => array('value'),
- '#delta' => 0,
- '#process' => array('options_buttons_elements_process'),
- );
- $types['options_onoff'] = array(
- '#input' => TRUE,
- '#columns' => array('value'),
- '#delta' => 0,
- '#process' => array('options_onoff_elements_process'),
- );
- return $types;
-}
-
-/**
* Implement hook_field_widget().
*/
function options_field_widget(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
- $element += array(
- '#type' => $instance['widget']['type'],
- '#default_value' => !empty($items) ? $items : array(),
- );
- return $element;
-}
-
-/**
- * Implement hook_field_widget_error().
- */
-function options_field_widget_error($element, $error) {
- $field_key = $element['#columns'][0];
- form_error($element[$field_key], $error['message']);
-}
+ // Abstract over the actual field columns, to allow different field types to
+ // reuse those widgets.
+ $value_key = key($field['columns']);
+ $multiple = $field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED;
+ // Form API 'checkboxes' do not suport 0 as an option, so we replace it with
+ // a placeholder within the form workflow.
+ $zero_placeholder = $instance['widget']['type'] == 'options_buttons' && $multiple;
+ // Collect available options for the field.
+ $options = options_get_options($field, $instance, $zero_placeholder);
+ // Put current field values in shape.
+ $default_value = _options_storage_to_form($items, $options, $value_key, $zero_placeholder);
-/**
- * Process an individual element.
- *
- * Build the form element. When creating a form using FAPI #process,
- * note that $element['#value'] is already set.
- *
- * The $field and $instance arrays are in $form['#fields'][$element['#field_name']].
- */
-function options_buttons_elements_process($element, &$form_state, $form) {
- $field = $form['#fields'][$element['#field_name']]['field'];
- $instance = $form['#fields'][$element['#field_name']]['instance'];
- $field_key = $element['#columns'][0];
-
- // See if this element is in the database format or the transformed format,
- // and transform it if necessary.
- if (is_array($element['#value']) && !array_key_exists($field_key, $element['#value'])) {
- $element['#value'] = options_data2form($element, $element['#default_value'], $field);
- }
- $options = options_options($field, $instance);
- $required = isset($element['#required']) ? $element['#required'] : $instance['required'];
- $multiple = isset($element['#multiple']) ? $element['#multiple'] : $field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED;
-
- // Incoming #value is an array (checkboxes) or integer (radios).
- $keys = $element['#value'][$field_key];
- if (!is_array($keys)) {
- $keys = array($keys);
- }
-
- // Multiple (checkboxes) need #default_value to be an array, and
- // non-multiple (radios) need a single default value. If #value is
- // empty we loop won't run, so initialize $value to the right type.
- $value = $multiple ? array() : '';
- foreach ($keys as $key) {
- if ($multiple) {
- $value[] = $key;
- }
- else {
- $value = $key;
+ switch ($instance['widget']['type']) {
+ case 'options_select':
+ $element += array(
+ '#type' => 'select',
+ '#default_value' => $default_value,
+ // Do not display a 'multiple' select box if there is only one option.
+ '#multiple' => $multiple && count($options) > 1,
+ '#options' => $options,
+ '#value_key' => $value_key,
+ '#element_validate' => array('options_field_widget_validate'),
+ );
break;
- }
- }
- // If required and there is one option, make it the default.
- if ($required && count($options) == 1) {
- $keys = array_keys($options);
- if ($multiple) {
- $value = $keys;
- }
- else {
- $value = $keys[0];
- }
- }
-
- $element[$field_key] = array(
- '#type' => $multiple ? 'checkboxes' : 'radios',
- '#title' => $element['#title'],
- '#description' => $element['#description'],
- '#required' => $required,
- '#multiple' => $multiple,
- '#options' => $options,
- '#default_value' => $value,
- );
+ case 'options_buttons':
+ $type = $multiple ? 'checkboxes' : 'radios';
+ // If required and there is one single option, preselect it.
+ if ($element['#required'] && count($options) == 1) {
+ $default_value = array(key($options));
+ }
+ $element += array(
+ '#type' => $type,
+ // Radio buttons need a scalar value.
+ '#default_value' => ($type == 'radios') ? reset($default_value) : $default_value,
+ '#options' => $options,
+ '#zero_placeholder' => $zero_placeholder,
+ '#value_key' => $value_key,
+ '#element_validate' => array('options_field_widget_validate'),
+ );
+ break;
- // Set #element_validate in a way that it will not wipe out other
- // validation functions already set by other modules.
- if (empty($element['#element_validate'])) {
- $element['#element_validate'] = array();
+ case 'options_onoff':
+ $keys = array_keys($options);
+ $off_value = (!empty($keys) && isset($keys[0])) ? $keys[0] : NULL;
+ $on_value = (!empty($keys) && isset($keys[1])) ? $keys[1] : NULL;
+ $element += array(
+ '#type' => 'checkbox',
+ '#title' => isset($options[$on_value]) ? $options[$on_value] : '',
+ '#default_value' => (isset($default_value[0]) && $default_value[0] == $on_value) ? 1 : 0,
+ '#on_value' => $on_value,
+ '#off_value' => $off_value,
+ '#value_key' => $value_key,
+ '#element_validate' => array('options_field_widget_validate'),
+ );
+ break;
}
- array_unshift($element['#element_validate'], 'options_validate');
- // Make sure field info will be available to the validator which
- // does not get the values in $form.
- $form_state['#fields'][$element['#field_name']] = $form['#fields'][$element['#field_name']];
return $element;
}
/**
- * Process an individual element.
- *
- * Build the form element. When creating a form using FAPI #process,
- * note that $element['#value'] is already set.
- *
- * The $field and $instance arrays are in $form['#fields'][$element['#field_name']].
+ * Form element validation handler for options element.
*/
-function options_select_elements_process($element, &$form_state, $form) {
- $field = $form['#fields'][$element['#field_name']]['field'];
- $instance = $form['#fields'][$element['#field_name']]['instance'];
- $field_key = $element['#columns'][0];
-
- // See if this element is in the database format or the transformed format,
- // and transform it if necessary.
- if (is_array($element['#value']) && !array_key_exists($field_key, $element['#value'])) {
- $element['#value'] = options_data2form($element, $element['#default_value'], $field);
- }
+function options_field_widget_validate($element, &$form_state) {
+ $field = $form_state['complete form']['#fields'][$element['#field_name']]['field'];
+ $instance = $form_state['complete form']['#fields'][$element['#field_name']]['instance'];
- $options = options_options($field, $instance);
- $element[$field_key] = array(
- '#type' => 'select',
- '#title' => $element['#title'],
- '#description' => $element['#description'],
- '#required' => isset($element['#required']) ? $element['#required'] : $instance['required'],
- '#multiple' => isset($element['#multiple']) ? $element['#multiple'] : $field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED,
- '#options' => $options,
- '#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : NULL,
- );
+ // Transpose selections from field => delta to delta => field, turning
+ // multiple selected options into multiple parent elements.
+ $items = _options_form_to_storage($element);
+ form_set_value($element, $items, $form_state);
- // Set #element_validate in a way that it will not wipe out other
- // validation functions already set by other modules.
- if (empty($element['#element_validate'])) {
- $element['#element_validate'] = array();
+ // Check that we don't exceed the allowed number of values.
+ if ($field['cardinality'] >= 2 && $field['cardinality'] != FIELD_CARDINALITY_UNLIMITED) {
+ if (count($items) > $field['cardinality']) {
+ form_error($element, t('%name: this field cannot hold more than @count values.', array('%name' => t($instance['label']), '@count' => $field['cardinality'])));
+ }
}
- array_unshift($element['#element_validate'], 'options_validate');
-
- // Make sure field info will be available to the validator which
- // does not get the values in $form.
- $form_state['#fields'][$element['#field_name']] = $form['#fields'][$element['#field_name']];
- return $element;
}
/**
- * Process an individual element.
- *
- * Build the form element. When creating a form using FAPI #process,
- * note that $element['#value'] is already set.
+ * Prepares the options for a field.
*/
-function options_onoff_elements_process($element, &$form_state, $form) {
- $field = $form['#fields'][$element['#field_name']]['field'];
- $instance = $form['#fields'][$element['#field_name']]['instance'];
- $field_key = $element['#columns'][0];
+function options_get_options($field, $instance, $zero_placeholder) {
+ // Check if there is a module hook for the option values, otherwise try
+ // list_allowed_values() for an options list.
+ // @todo This should be turned into a hook_options_allowed_values(), exposed
+ // by options.module.
+ $function = $field['module'] . '_allowed_values';
+ $options = function_exists($function) ? $function($field) : (array) list_allowed_values($field);
- // See if this element is in the database format or the transformed format,
- // and transform it if necessary.
- if (is_array($element['#value']) && !array_key_exists($field_key, $element['#value'])) {
- $element['#value'] = options_data2form($element, $element['#default_value'], $field);
+ // Substitute the '_0' placeholder.
+ if ($zero_placeholder) {
+ $values = array_keys($options);
+ // Use a strict comparison, because 0 == 'any string'.
+ $index = array_search(0, $values, TRUE);
+ if ($index !== FALSE) {
+ $values[$index] = '_0';
+ $options = array_combine($values, array_values($options));
+ }
}
- $options = options_options($field, $instance);
- $keys = array_keys($options);
- $on_value = (!empty($keys) && isset($keys[1])) ? $keys[1] : NULL;
- $element[$field_key] = array(
- '#type' => 'checkbox',
- '#title' => isset($options[$on_value]) ? $options[$on_value] : '',
- '#description' => $element['#description'],
- '#default_value' => isset($element['#value'][$field_key][0]) ? $element['#value'][$field_key][0] == $on_value : FALSE,
- '#return_value' => $on_value,
- );
- // Set #element_validate in a way that it will not wipe out other
- // validation functions already set by other modules.
- if (empty($element['#element_validate'])) {
- $element['#element_validate'] = array();
+ // Add an empty choice for
+ // - non required radios
+ // - non required selects
+ if (!$instance['required']) {
+ if (($instance['widget']['type'] == 'options_buttons' && ($field['cardinality'] == 1)) || ($instance['widget']['type'] == 'options_select')) {
+ $options = array('_none' => theme('options_none', array('instance' => $instance))) + $options;
+ }
}
- array_unshift($element['#element_validate'], 'options_validate');
-
- // Make sure field info will be available to the validator which
- // does not get the values in $form.
- $form_state['#fields'][$element['#field_name']] = $form['#fields'][$element['#field_name']];
- return $element;
+ return $options;
}
/**
- * FAPI function to validate options element.
+ * Transforms stored field values into the format the widgets need.
*/
-function options_validate($element, &$form_state) {
- // Transpose selections from field => delta to delta => field,
- // turning cardinality selected options into cardinality parent elements.
- // Immediate parent is the delta, need to get back to parent's parent
- // to create cardinality elements.
- $field = $form_state['#fields'][$element['#field_name']]['field'];
- $items = options_form2data($element, $field);
- form_set_value($element, $items, $form_state);
-
- // Check we don't exceed the allowed number of values.
- if ($field['cardinality'] >= 2) {
- // Filter out 'none' value (if present, will always be in key 0)
- $field_key = $element['#columns'][0];
- if (isset($items[0][$field_key]) && $items[0][$field_key] === '') {
- unset($items[0]);
- }
- if (count($items) > $field['cardinality']) {
- $field_key = $element['#columns'][0];
- form_error($element[$field_key], t('%name: this field cannot hold more that @count values.', array('%name' => t($field['widget']['label']), '@count' => $field['cardinality'])));
+function _options_storage_to_form($items, $options, $column, $zero_placeholder) {
+ $items_transposed = options_array_transpose($items);
+ $values = (isset($items_transposed[$column]) && is_array($items_transposed[$column])) ? $items_transposed[$column] : array();
+
+ // Substitute the '_0' placeholder.
+ if ($zero_placeholder) {
+ $index = array_search('0', $values);
+ if ($index !== FALSE) {
+ $values[$index] = '_0';
}
}
+
+ // Discard values that are not in the current list of options.
+ $values = array_values(array_intersect($values, array_keys($options)));
+ return $values;
}
/**
- * Helper function to transpose the values as stored in the database
- * to the format the widget needs. Can be called anywhere this
- * transformation is needed.
+ * Transforms submitted form values into field storage format.
*/
-function options_data2form($element, $items, $field) {
- $field_key = $element['#columns'][0];
- $field = field_info_field($element['#field_name']);
- $instance = field_info_instance($element['#object_type'], $element['#field_name'], $element['#bundle']);
- $options = options_options($field, $instance);
+function _options_form_to_storage($element) {
+ $values = array_values((array) $element['#value']);
- $items_transposed = options_transpose_array_rows_cols($items);
- $values = (isset($items_transposed[$field_key]) && is_array($items_transposed[$field_key])) ? $items_transposed[$field_key] : array();
- $keys = array();
- foreach ($values as $value) {
- $key = array_search($value, array_keys($options));
- if (isset($key)) {
- $keys[] = $value;
- }
+ // On/off checkbox: transform '0 / 1' into the 'on / off' values.
+ if ($element['#type'] == 'checkbox') {
+ $values = array($values[0] ? $element['#on_value'] : $element['#off_value']);
}
- if ($field['cardinality'] || $element['#type'] == 'options_onoff') {
- return array($field_key => $keys);
- }
- else {
- return !empty($keys) ? array($field_key => $value) : array();
- }
-}
-/**
- * Helper function to transpose the values returned by submitting the widget
- * to the format to be stored in the field. Can be called anywhere this
- * transformation is needed.
- */
-function options_form2data($element, $field) {
- $field_key = $element['#columns'][0];
- $field = field_info_field($element['#field_name']);
- $instance = field_info_instance($element['#object_type'], $element['#field_name'], $element['#bundle']);
- $items = (array) $element[$field_key]['#value'];
- $options = options_options($field, $instance);
-
- $values = array_values($items);
+ // Substitute the '_0' placeholder.
+ if (!empty($element['#zero_placeholder'])) {
+ $index = array_search('_0', $values);
+ if ($index !== FALSE) {
+ $values[$index] = 0;
+ }
+ }
- if ($element['#type'] == 'options_onoff' && ($values[0] === 0)) {
- $keys = array_keys($options);
- $values = array(array_key_exists(0, $keys) ? $keys[0] : NULL);
+ // Filter out the 'none' option. Use a strict comparison, because
+ // 0 == 'any string'.
+ $index = array_search('_none', $values, TRUE);
+ if ($index !== FALSE) {
+ unset($values[$index]);
}
+ // Make sure we populate at least an empty value.
if (empty($values)) {
- $values[] = NULL;
+ $values = array(NULL);
}
- $result = options_transpose_array_rows_cols(array($field_key => $values));
+
+ $result = options_array_transpose(array($element['#value_key'] => $values));
return $result;
}
/**
- * Manipulate a 2D array to reverse rows and columns.
+ * Manipulates a 2D array to reverse rows and columns.
*
* The default data storage for fields is delta first, column names second.
* This is sometimes inconvenient for field modules, so this function can be
@@ -362,7 +232,7 @@ function options_form2data($element, $field) {
* @return
* The transposed array.
*/
-function options_transpose_array_rows_cols($array) {
+function options_array_transpose($array) {
$result = array();
if (is_array($array)) {
foreach ($array as $key1 => $value1) {
@@ -380,24 +250,10 @@ function options_transpose_array_rows_cols($array) {
}
/**
- * Helper function for finding the allowed values list for a field.
- *
- * See if there is a module hook for the option values.
- * Otherwise, try list_allowed_values() for an options list.
+ * Implement hook_field_widget_error().
*/
-function options_options($field, $instance) {
- $function = $field['module'] . '_allowed_values';
- $options = function_exists($function) ? $function($field) : (array) list_allowed_values($field);
- // Add an empty choice for :
- // - non required radios
- // - non required selects
- if (!$instance['required']) {
- if ((in_array($instance['widget']['type'], array('options_buttons', 'node_reference_buttons', 'user_reference_buttons')) && !$field['cardinality'])
- || (in_array($instance['widget']['type'], array('options_select', 'node_reference_select', 'user_reference_select')))) {
- $options = array('' => theme('options_none', array('instance' => $instance))) + $options;
- }
- }
- return $options;
+function options_field_widget_error($element, $error) {
+ form_error($element, $error['message']);
}
/**
@@ -408,40 +264,10 @@ function theme_options_none($variables) {
$instance = $variables['instance'];
switch ($instance['widget']['type']) {
case 'options_buttons':
- case 'node_reference_buttons':
- case 'user_reference_buttons':
return t('N/A');
case 'options_select':
- case 'node_reference_select':
- case 'user_reference_select':
return t('- None -');
default :
return '';
}
}
-
-/**
- * FAPI themes for options.
- *
- * The select, checkboxes or radios are already rendered by the
- * select, checkboxes, or radios themes and the HTML output
- * lives in $variables['element']['#children']. Override this theme to
- * make custom changes to the output.
- *
- * $variables['element']['#field_name'] contains the field name
- * $variables['element']['#delta] is the position of this element in the group
- */
-function theme_options_select($variables) {
- $element = $variables['element'];
- return $element['#children'];
-}
-
-function theme_options_onoff($variables) {
- $element = $variables['element'];
- return $element['#children'];
-}
-
-function theme_options_buttons($variables) {
- $element = $variables['element'];
- return $element['#children'];
-} \ No newline at end of file