' . t('About') . ''; $output .= '

' . t('The Options module defines checkbox, selection, and other input widgets for the Field module. See the Field module help page for more information about fields.', array('@field-help' => url('admin/help/field'))) . '

'; return $output; } } /** * Implements hook_theme(). */ function options_theme() { return array( 'options_none' => array( 'variables' => array('instance' => NULL, 'option' => NULL), ), ); } /** * Implements hook_field_widget_info(). * * Field type modules willing to use those widgets should: * - Use hook_field_widget_info_alter() to append their field own types to the * list of types supported by the widgets, * - Implement hook_options_list() to provide the list of options. * See list.module. */ function options_field_widget_info() { return array( 'options_select' => array( 'label' => t('Select list'), 'field types' => array(), 'behaviors' => array( 'multiple values' => FIELD_BEHAVIOR_CUSTOM, ), ), 'options_buttons' => array( 'label' => t('Check boxes/radio buttons'), 'field types' => array(), 'behaviors' => array( 'multiple values' => FIELD_BEHAVIOR_CUSTOM, ), ), 'options_onoff' => array( 'label' => t('Single on/off checkbox'), 'field types' => array(), 'behaviors' => array( 'multiple values' => FIELD_BEHAVIOR_CUSTOM, ), 'settings' => array('display_label' => 0), ), ); } /** * Implements hook_field_widget_form(). */ function options_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) { // Abstract over the actual field columns, to allow different field types to // reuse those widgets. $value_key = key($field['columns']); $type = str_replace('options_', '', $instance['widget']['type']); $multiple = $field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED; $required = $element['#required']; $has_value = isset($items[0][$value_key]); $properties = _options_properties($type, $multiple, $required, $has_value); // Prepare the list of options. $options = _options_get_options($field, $instance, $properties); // Put current field values in shape. $default_value = _options_storage_to_form($items, $options, $value_key, $properties); switch ($type) { case '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, ); break; case 'buttons': // If required and there is one single option, preselect it. if ($required && count($options) == 1) { reset($options); $default_value = array(key($options)); } $element += array( '#type' => $multiple ? 'checkboxes' : 'radios', // Radio buttons need a scalar value. '#default_value' => $multiple ? $default_value : reset($default_value), '#options' => $options, ); break; case 'onoff': $keys = array_keys($options); $off_value = array_shift($keys); $on_value = array_shift($keys); $element += array( '#type' => 'checkbox', '#default_value' => (isset($default_value[0]) && $default_value[0] == $on_value) ? 1 : 0, '#on_value' => $on_value, '#off_value' => $off_value, ); // Override the title from the incoming $element. $element['#title'] = isset($options[$on_value]) ? $options[$on_value] : ''; if ($instance['widget']['settings']['display_label']) { $element['#title'] = $instance['label']; } break; } $element += array( '#value_key' => $value_key, '#element_validate' => array('options_field_widget_validate'), '#properties' => $properties, ); return $element; } /** * Implements hook_field_widget_settings_form(). */ function options_field_widget_settings_form($field, $instance) { $form = array(); if ($instance['widget']['type'] == 'options_onoff') { $form['display_label'] = array( '#type' => 'checkbox', '#title' => t('Use field label instead of the "On value" as label'), '#default_value' => $instance['widget']['settings']['display_label'], '#weight' => -1, ); } return $form; } /** * Form element validation handler for options element. */ function options_field_widget_validate($element, &$form_state) { if ($element['#required'] && $element['#value'] == '_none') { form_error($element, t('!name field is required.', array('!name' => $element['#title']))); } // 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); } /** * Describes the preparation steps required by each widget. */ function _options_properties($type, $multiple, $required, $has_value) { $base = array( 'filter_xss' => FALSE, 'strip_tags' => FALSE, 'empty_option' => FALSE, 'optgroups' => FALSE, ); $properties = array(); switch ($type) { case 'select': $properties = array( // Select boxes do not support any HTML tag. 'strip_tags' => TRUE, 'optgroups' => TRUE, ); if ($multiple) { // Multiple select: add a 'none' option for non-required fields. if (!$required) { $properties['empty_option'] = 'option_none'; } } else { // Single select: add a 'none' option for non-required fields, // and a 'select a value' option for required fields that do not come // with a value selected. if (!$required) { $properties['empty_option'] = 'option_none'; } else if (!$has_value) { $properties['empty_option'] = 'option_select'; } } break; case 'buttons': $properties = array( 'filter_xss' => TRUE, ); // Add a 'none' option for non-required radio buttons. if (!$required && !$multiple) { $properties['empty_option'] = 'option_none'; } break; case 'onoff': $properties = array( 'filter_xss' => TRUE, ); break; } return $properties + $base; } /** * Collects the options for a field. */ function _options_get_options($field, $instance, $properties) { // Get the list of options. $options = (array) module_invoke($field['module'], 'options_list', $field); // Sanitize the options. _options_prepare_options($options, $properties); if (!$properties['optgroups']) { $options = options_array_flatten($options); } if ($properties['empty_option']) { $label = theme('options_none', array('instance' => $instance, 'option' => $properties['empty_option'])); $options = array('_none' => $label) + $options; } return $options; } /** * Sanitizes the options. * * The function is recursive to support optgroups. */ function _options_prepare_options(&$options, $properties) { foreach ($options as $value => $label) { // Recurse for optgroups. if (is_array($label)) { _options_prepare_options($options[$value], $properties); } else { if ($properties['strip_tags']) { $options[$value] = strip_tags($label); } if ($properties['filter_xss']) { $options[$value] = field_filter_xss($label); } } } } /** * Transforms stored field values into the format the widgets need. */ function _options_storage_to_form($items, $options, $column, $properties) { $items_transposed = options_array_transpose($items); $values = (isset($items_transposed[$column]) && is_array($items_transposed[$column])) ? $items_transposed[$column] : array(); // Discard values that are not in the current list of options. Flatten the // array if needed. if ($properties['optgroups']) { $options = options_array_flatten($options); } $values = array_values(array_intersect($values, array_keys($options))); return $values; } /** * Transforms submitted form values into field storage format. */ function _options_form_to_storage($element) { $values = array_values((array) $element['#value']); $properties = $element['#properties']; // 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']); } // Filter out the 'none' option. Use a strict comparison, because // 0 == 'any string'. if ($properties['empty_option']) { $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 = array(NULL); } $result = options_array_transpose(array($element['#value_key'] => $values)); return $result; } /** * 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 * used to present the data in an alternate format. * * @param $array * The array to be transposed. It must be at least two-dimensional, and * the subarrays must all have the same keys or behavior is undefined. * @return * The transposed array. */ function options_array_transpose($array) { $result = array(); if (is_array($array)) { foreach ($array as $key1 => $value1) { if (is_array($value1)) { foreach ($value1 as $key2 => $value2) { if (!isset($result[$key2])) { $result[$key2] = array(); } $result[$key2][$key1] = $value2; } } } } return $result; } /** * Flattens an array of allowed values. * * @param $array * A single or multidimensional array. * @return * A flattened array. */ function options_array_flatten($array) { $result = array(); if (is_array($array)) { foreach ($array as $key => $value) { if (is_array($value)) { $result += options_array_flatten($value); } else { $result[$key] = $value; } } } return $result; } /** * Implements hook_field_widget_error(). */ function options_field_widget_error($element, $error, $form, &$form_state) { form_error($element, $error['message']); } /** * Returns HTML for the label for the empty value for options that are not required. * * The default theme will display N/A for a radio list and '- None -' for a select. * * @param $variables * An associative array containing: * - instance: An array representing the widget requesting the options. * * @ingroup themeable */ function theme_options_none($variables) { $instance = $variables['instance']; $option = $variables['option']; $output = ''; switch ($instance['widget']['type']) { case 'options_buttons': $output = t('N/A'); break; case 'options_select': $output = ($option == 'option_none' ? t('- None -') : t('- Select a value -')); break; } return $output; }