Newer
Older
<?php
// $Id$
/**
* @file
* Defines selection, check box and radio button widgets for text and numeric fields.
*/
Angie Byron
committed
/**
* Implements hook_help().
*/
function options_help($path, $arg) {
switch ($path) {
case 'admin/help#options':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('The Options module defines checkbox, selection, and other input widgets for the Field module. See the <a href="@field-help">Field module help page</a> for more information about fields.', array('@field-help' => url('admin/help/field'))) . '</p>';
return $output;
}
}
Dries Buytaert
committed
* Implements hook_theme().
*/
function options_theme() {
return array(
'options_none' => array(
'variables' => array('instance' => NULL),
Angie Byron
committed
),
);
}
/**
Dries Buytaert
committed
* Implements hook_field_widget_info().
Dries Buytaert
committed
*
* 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'),
Dries Buytaert
committed
'field types' => array(),
'behaviors' => array(
'multiple values' => FIELD_BEHAVIOR_CUSTOM,
),
),
'options_buttons' => array(
'label' => t('Check boxes/radio buttons'),
Dries Buytaert
committed
'field types' => array(),
'behaviors' => array(
'multiple values' => FIELD_BEHAVIOR_CUSTOM,
),
),
'options_onoff' => array(
'label' => t('Single on/off checkbox'),
Dries Buytaert
committed
'field types' => array(),
'behaviors' => array(
'multiple values' => FIELD_BEHAVIOR_CUSTOM,
),
),
);
}
/**
Dries Buytaert
committed
* Implements hook_field_widget_form().
Dries Buytaert
committed
function options_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
Angie Byron
committed
// Abstract over the actual field columns, to allow different field types to
// reuse those widgets.
$value_key = key($field['columns']);
Dries Buytaert
committed
$type = str_replace('options_', '', $instance['widget']['type']);
Angie Byron
committed
$multiple = $field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED;
Dries Buytaert
committed
$required = $element['#required'];
$properties = _options_properties($type, $multiple, $required);
// Prepare the list of options.
$options = _options_get_options($field, $instance, $properties);
Angie Byron
committed
// Put current field values in shape.
Dries Buytaert
committed
$default_value = _options_storage_to_form($items, $options, $value_key, $properties);
Angie Byron
committed
Dries Buytaert
committed
switch ($type) {
case 'select':
Angie Byron
committed
$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,
);
Angie Byron
committed
Dries Buytaert
committed
case 'buttons':
Angie Byron
committed
// If required and there is one single option, preselect it.
Dries Buytaert
committed
if ($required && count($options) == 1) {
reset($options);
Angie Byron
committed
$default_value = array(key($options));
}
$element += array(
Dries Buytaert
committed
'#type' => $multiple ? 'checkboxes' : 'radios',
Angie Byron
committed
// Radio buttons need a scalar value.
Dries Buytaert
committed
'#default_value' => $multiple ? $default_value : reset($default_value),
Angie Byron
committed
'#options' => $options,
);
break;
Dries Buytaert
committed
case 'onoff':
Angie Byron
committed
$keys = array_keys($options);
Dries Buytaert
committed
$off_value = array_shift($keys);
$on_value = array_shift($keys);
Angie Byron
committed
$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,
);
Dries Buytaert
committed
// Override the title from the incoming $element.
$element['#title'] = isset($options[$on_value]) ? $options[$on_value] : '';
Angie Byron
committed
break;
Dries Buytaert
committed
$element += array(
'#value_key' => $value_key,
'#element_validate' => array('options_field_widget_validate'),
'#properties' => $properties,
);
return $element;
}
/**
Angie Byron
committed
* Form element validation handler for options element.
Angie Byron
committed
function options_field_widget_validate($element, &$form_state) {
// 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);
Dries Buytaert
committed
* Describes the preparation steps required by each widget.
Dries Buytaert
committed
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
function _options_properties($type, $multiple, $required) {
$base = array(
'zero_placeholder' => FALSE,
'filter_xss' => FALSE,
'strip_tags' => FALSE,
'empty_value' => FALSE,
'optgroups' => FALSE,
);
switch ($type) {
case 'select':
$properties = array(
// Select boxes do not support any HTML tag.
'strip_tags' => TRUE,
'empty_value' => !$required,
'optgroups' => TRUE,
);
break;
case 'buttons':
$properties = array(
'filter_xss' => TRUE,
// Form API 'checkboxes' do not suport 0 as an option, so we replace it with
// a placeholder within the form workflow.
'zero_placeholder' => $multiple,
// Checkboxes do not need a 'none' choice.
'empty_value' => !$required && !$multiple,
);
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_value']) {
$options = array('_none' => theme('options_none', array('instance' => $instance))) + $options;
}
return $options;
}
Dries Buytaert
committed
/**
* Sanitizes the options.
*
* The function is recursive to support optgroups.
*/
function _options_prepare_options(&$options, $properties) {
Angie Byron
committed
// Substitute the '_0' placeholder.
Dries Buytaert
committed
if ($properties['zero_placeholder']) {
Angie Byron
committed
$values = array_keys($options);
Dries Buytaert
committed
$labels = array_values($options);
Angie Byron
committed
// Use a strict comparison, because 0 == 'any string'.
$index = array_search(0, $values, TRUE);
Dries Buytaert
committed
if ($index !== FALSE && !is_array($options[$index])) {
Angie Byron
committed
$values[$index] = '_0';
Dries Buytaert
committed
$options = array_combine($values, $labels);
Angie Byron
committed
}
Dries Buytaert
committed
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);
}
Angie Byron
committed
}
}
}
/**
Angie Byron
committed
* Transforms stored field values into the format the widgets need.
Dries Buytaert
committed
function _options_storage_to_form($items, $options, $column, $properties) {
Angie Byron
committed
$items_transposed = options_array_transpose($items);
$values = (isset($items_transposed[$column]) && is_array($items_transposed[$column])) ? $items_transposed[$column] : array();
// Substitute the '_0' placeholder.
Dries Buytaert
committed
if ($properties['zero_placeholder']) {
Angie Byron
committed
$index = array_search('0', $values);
if ($index !== FALSE) {
$values[$index] = '_0';
Angie Byron
committed
Dries Buytaert
committed
// Discard values that are not in the current list of options. Flatten the
// array if needed.
if ($properties['optgroups']) {
$options = options_array_flatten($options);
}
Angie Byron
committed
$values = array_values(array_intersect($values, array_keys($options)));
return $values;
Angie Byron
committed
* Transforms submitted form values into field storage format.
Angie Byron
committed
function _options_form_to_storage($element) {
$values = array_values((array) $element['#value']);
Dries Buytaert
committed
$properties = $element['#properties'];
Angie Byron
committed
// 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']);
Angie Byron
committed
// Substitute the '_0' placeholder.
Dries Buytaert
committed
if ($properties['zero_placeholder']) {
Angie Byron
committed
$index = array_search('_0', $values);
if ($index !== FALSE) {
$values[$index] = 0;
}
}
Angie Byron
committed
// Filter out the 'none' option. Use a strict comparison, because
// 0 == 'any string'.
Dries Buytaert
committed
if ($properties['empty_value']) {
$index = array_search('_none', $values, TRUE);
if ($index !== FALSE) {
unset($values[$index]);
}
Angie Byron
committed
// Make sure we populate at least an empty value.
if (empty($values)) {
Angie Byron
committed
$values = array(NULL);
Angie Byron
committed
$result = options_array_transpose(array($element['#value_key'] => $values));
return $result;
}
/**
Angie Byron
committed
* 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.
*/
Angie Byron
committed
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;
}
Dries Buytaert
committed
/**
* 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;
}
Dries Buytaert
committed
* Implements hook_field_widget_error().
Angie Byron
committed
function options_field_widget_error($element, $error) {
form_error($element, $error['message']);
}
/**
* Theme the label for the empty value for options that are not required.
* The default theme will display N/A for a radio list and blank for a select.
*/
Dries Buytaert
committed
function theme_options_none($variables) {
$instance = $variables['instance'];