'',
'available_options_php' => '',
'markup_available_options_php' => t('<none>'),
'other' => t('Other'),
'other_title' => '',
'other_unknown_defaults' => 'other',
'other_size' => 60,
);
return array(
'select_or_other' => array(
'label' => t('Select (or other) list'),
'field types' => $field_types,
'behaviors' => array(
'multiple values' => FIELD_BEHAVIOR_CUSTOM,
'default value' => FIELD_BEHAVIOR_DEFAULT,
),
'settings' => $settings,
'weight' => 2,
),
'select_or_other_sort' => array(
'label' => t('Select (or other) drag and drop lists [deprecated in favor of Field Collection]'),
'field types' => $field_types,
'behaviors' => array(
'multiple values' => FIELD_BEHAVIOR_DEFAULT,
'default value' => FIELD_BEHAVIOR_DEFAULT,
),
'settings' => $settings,
'weight' => 2,
),
'select_or_other_buttons' => array(
'label' => t('Select (or other) check boxes/radio buttons'),
'field types' => $field_types,
'behaviors' => array(
'multiple values' => FIELD_BEHAVIOR_CUSTOM,
'default value' => FIELD_BEHAVIOR_DEFAULT,
),
'settings' => $settings,
'weight' => 2,
),
);
}
/**
* Prepare a single option.
*/
function select_or_other_field_widget_form_prepare_option(&$options, $key, $opt, $settings) {
$opt = trim($opt);
if (empty($opt)) {
return;
}
// Sanitize the user input with a permissive filter.
$opt = filter_xss($opt);
// If option has a key specified
if (strpos($opt, '|') !== FALSE && empty($settings['available_options_php'])) {
list($key, $value) = explode('|', $opt);
$options[$key] = (isset($value) && $value !== '') ? html_entity_decode($value) : $key;
}
// If options from PHP
elseif (!empty($settings['available_options_php'])) {
$options[$key] = html_entity_decode($opt);
}
// If option has no key specified and is not from PHP
else {
$options[$opt] = html_entity_decode($opt);
}
}
/***
* Get Options from settings
* in a separate scope for assurance using 'available_options_php'
* @returns array
***/
function _select_or_other_field_widget_get_available_options($select_or_other_field_widget_settings) {
// Create options - similar to code from content_allowed_values().
if (!empty($select_or_other_field_widget_settings['available_options_php'])) {
ob_start();
$select_or_other_field_widget_list = eval($select_or_other_field_widget_settings['available_options_php']);
ob_end_clean();
} else {
$select_or_other_field_widget_list = explode("\n", $select_or_other_field_widget_settings['available_options']);
}
return $select_or_other_field_widget_list;
}
/**
* Prepare options for the widget list.
*/
function select_or_other_field_widget_form_prepare_options($field, $instance, $has_value = FALSE) {
$options = array();
$settings = &$instance['widget']['settings'];
$list = _select_or_other_field_widget_get_available_options($settings);
foreach ($list as $key => $opt) {
if (is_array($opt)) {
$optgroup_options = array();
foreach ($opt as $optgroup_key => $optgroup_opt) {
select_or_other_field_widget_form_prepare_option($optgroup_options, $optgroup_key, $optgroup_opt, $settings);
$options[$key] = $optgroup_options;
}
}
else {
select_or_other_field_widget_form_prepare_option($options, $key, $opt, $settings);
}
}
$required = (isset($instance['required']) && $instance['required']);
$multiple = (($instance['widget']['type'] == 'select_or_other' && $field['cardinality'] == -1)
|| ($instance['widget']['type'] == 'select_or_other_sort'));
$multiple_checkbox = ($instance['widget']['type'] == 'select_or_other_buttons' && $field['cardinality'] == -1);
$empty_option = array('_none' => theme('select_or_other_none', array('instance' => $instance)));
// Multiple select.
if ($multiple) {
// Add a 'none' option for non-required fields.
if (!$required) {
$options = $empty_option + $options;
}
}
// Single select.
elseif (!$multiple_checkbox) {
// 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) {
$options = $empty_option + $options;
}
}
// @todo: This isset() can probably be taken out in drupal 8.
if (isset($settings['sort_options']) && $settings['sort_options']) {
natcasesort($options);
}
return $options;
}
/**
* Implements hook_field_widget_form().
*/
function select_or_other_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
// Construct the element.
$element = $element + array(
'#type' => 'select_or_other',
'#other' => isset($instance['widget']['settings']['other']) ? $instance['widget']['settings']['other'] : t('Other'),
'#other_title' => !empty($instance['widget']['settings']['other_title']) ? $instance['widget']['settings']['other_title'] : NULL,
'#other_size' => $instance['widget']['settings']['other_size'],
'#default_value' => isset($items[$delta]['value']) ? $items[$delta]['value'] : NULL,
'#options' => select_or_other_field_widget_form_prepare_options($field, $instance, !empty($items[$delta])),
'#description' => isset($instance['description']) ? $instance['description'] : '',
'#multiple' => $field['cardinality'] == 1 ? FALSE : $field['cardinality'],
'#required' => $instance['required'],
//'#other_delimiter' => $field['widget']['settings']['other_delimiter'] == 'FALSE' ? FALSE : $field['widget']['settings']['other_delimiter'],
'#other_delimiter' => FALSE,
'#other_unknown_defaults' => isset($instance['widget']['settings']['other_unknown_defaults']) ? $instance['widget']['settings']['other_unknown_defaults'] : 'other',
'#element_validate' => array('select_or_other_field_widget_validate'),
'#field_widget' => $instance['widget']['type'],
);
if (!empty($field['settings']['max_length'])) {
$element['#maxlength'] = $field['settings']['max_length'];
}
// Set select type's.
switch ($instance['widget']['type']) {
case 'select_or_other':
$element['#select_type'] = 'select';
break;
case 'select_or_other_sort':
$element['#select_type'] = 'select';
// Also disable multiples for this select type.
$element['#multiple'] = FALSE;
break;
case 'select_or_other_buttons':
$element['#select_type'] = $field['cardinality'] == 1 ? 'radios' : 'checkboxes';
break;
}
// In situations where we handle our own multiples (checkboxes and multiple selects) set defaults differently.
if ($element['#multiple']) {
$element['#default_value'] = array();
foreach ($items as $delta => $item) {
$element['#default_value'][$delta] = $item['value'];
}
}
return $element;
}
/**
* Implements hook_field_widget_settings_form().
*/
function select_or_other_field_widget_settings_form($field, $instance) {
$form = array();
$settings = &$instance['widget']['settings'];
$form['available_options'] = array(
'#type' => 'textarea',
'#title' => t('Available options'),
'#description' => t('A list of values that are, by default, available for selection. Enter one value per line, in the format key|label. The key is the value that will be stored in the database, and the label is what will be displayed to the user.'),
'#default_value' => isset($settings['available_options']) ? $settings['available_options'] : '',
'#element_validate' => array('select_or_other_field_widget_settings_validate'),
);
if (user_access('use PHP for settings')) {
$form['available_options_php'] = array(
'#type' => 'textarea',
'#title' => t('Available options PHP'),
'#default_value' => !empty($settings['available_options_php']) ? $settings['available_options_php'] : '',
'#rows' => 6,
'#description' => t('Advanced usage only: PHP code that returns a keyed array of available options. Should not include <?php ?> delimiters. If this field is filled out, the array returned by this code will override the available options list above.'),
);
}
else {
$form['available_options_php'] = array(
'#type' => 'value',
'#value' => !empty($settings['available_options_php']) ? $settings['available_options_php'] : '',
);
if (!empty($settings['available_options_php'])) {
$form['markup_available_options_php'] = array(
'#type' => 'item',
'#title' => t('Available options PHP'),
'#value' => '' . check_plain($settings['available_options_php']) . '
',
'#description' => empty($settings['available_options_php']) ? t("You're not allowed to input PHP code.") : t('This PHP code was set by an administrator and will override the allowed values list above.'),
);
}
}
$form['other'] = array(
'#type' => 'textfield',
'#title' => t('Other option'),
'#description' => t('Label for the option that the user will choose when they want to supply an other value.'),
'#default_value' => isset($settings['other']) ? $settings['other'] : t('Other'),
'#required' => TRUE,
);
$form['other_title'] = array(
'#type' => 'textfield',
'#title' => t('Other field title'),
'#description' => t('Label for the field in which the user will supply an other value.'),
'#default_value' => isset($settings['other_title']) ? $settings['other_title'] : '',
);
$form['other_unknown_defaults'] = array(
'#type' => 'select',
'#title' => t('Other value as default value'),
'#description' => t("If any incoming default values do not appear in available options (i.e. set as other values), what should happen?"),
'#options' => array(
'other' => t('Add the values to the other textfield'),
'append' => t('Append the values to the current list'),
'available' => t('Append the values to the available options'),
'ignore' => t('Ignore the values'),
),
'#default_value' => isset($settings['other_unknown_defaults']) ? $settings['other_unknown_defaults'] : 'other',
'#required' => TRUE,
);
$form['other_size'] = array(
'#type' => 'textfield',
'#title' => t('Other field size'),
'#default_value' => $settings['other_size'],
'#required' => TRUE,
'#element_validate' => array('element_validate_integer_positive'),
);
$form['sort_options'] = array(
'#type' => 'checkbox',
'#title' => t('Sort options'),
'#description' => t("Sorts the options in the list alphabetically by value."),
'#default_value' => isset($settings['sort_options']) ? $settings['sort_options'] : 0,
);
/*
There are design issues with saving multiple other values with some field widgets - this needs a rethink.
$form['other_delimiter'] = array(
'#type' => 'textfield',
'#title' => t('Other delimiter'),
'#description' => t("Delimiter string to delimit multiple 'other' values into the other textfield. If you enter FALSE only the last value will be used."),
'#default_value' => isset($settings['other_delimiter']) ? $settings['other_delimiter'] : ', ',
'#required' => TRUE,
'#size' => 5,
);
*/
$form['#validate'][] = 'select_or_other_field_widget_settings_validate';
return $form;
}
/**
* Validate callback for a Select (or other) field widget settings form.
*/
function select_or_other_field_widget_settings_validate($element, &$form_state, $form) {
$settings = &$form_state['values']['instance']['widget']['settings'];
if (empty($settings['available_options']) && empty($settings['available_options_php'])) {
form_set_error(implode('][', $element['#parents']), t('You must provide Available options.'));
}
}
/**
* Transforms submitted form values into field storage format.
* Align with Drupal core select list, NULL (-None-) value.
* http://drupal.org/node/1830090
*/
function _select_or_other_options_form_to_storage($value) {
if (isset($value[0])) {
$index = array_search('_none', $value[0], TRUE);
if ($index !== FALSE) {
unset($value[0][$index]);
}
}
return $value;
}
/**
* Element validate callback for a Select (or other) field widget.
*/
function select_or_other_field_widget_validate($element, &$form_state) {
//$field_name = $element['#parents'][count($element['#parents']) - 2];
$field_name = $element['#field_name'];
$field_info = field_info_field($field_name);
$other_selected = FALSE;
if (is_array($element['select']['#value']) && isset($element['select']['#value']['select_or_other'])) {
// This is a multiselect. assoc arrays
$other_selected = TRUE;
$value = $element['select']['#value'];
unset($value['select_or_other']);
$value[$element['other']['#value']] = $element['other']['#value'];
}
elseif (is_string($element['select']['#value']) && $element['select']['#value'] == 'select_or_other') {
// This is a single select.
$other_selected = TRUE;
$value = $element['other']['#value'];
}
else {
$value = $element['select']['#value'];
}
if ($other_selected && !$element['other']['#value']) {
form_error($element['other'], t('!name: !title is required', array('!name' => t($element['select']['#title']), '!title' => $element['#other'])));
}
if (isset($value) && $value !== "") {
if (in_array($element['#field_widget'], array('select_or_other', 'select_or_other_buttons'))) {
// Filter out 'none' value (if present, will always be in key 0)
if (isset($items[0]['value']) && $items[0]['value'] === '') {
unset($items[0]);
}
if ($element['#multiple'] >= 2 && count($value) > $element['#multiple']) {
form_error($element['select'], t('!name: cannot hold more than @count values.', array('!name' => t($element['select']['#title']), '@count' => $element['#multiple'])));
}
$delta = 0;
$values = array();
foreach ((array)$value as $v) {
if ($field_info['type'] == 'number_integer' && !preg_match('/^-?\d+$/', $v) && $v != '_none') {
form_error($element, t('!name field must be a valid integer.', array('!name' => t($element['select']['#title']))));
break;
}
if (($field_info['type'] == 'number_float' || $field_info['type'] == 'number_decimal') && !is_numeric($v)) {
form_error($element, t('!name field must be a valid integer or decimal.', array('!name' => t($element['select']['#title']))));
break;
}
elseif ($field_info['type'] == 'text' && drupal_strlen($v) > $field_info['settings']['max_length']) {
form_error($element, t('!name field must be a string at most @max characters long.', array('!name' => t($element['select']['#title']), '@max' => $field_info['settings']['max_length'])));
break;
}
$values[$delta++]['value'] = $v;
}
$value = $values;
}
elseif ($element['#field_widget'] == 'select_or_other_sort') {
$value = array('value' => $value);
}
// Align with Drupal core select list, NULL (-None-) value.
// http://drupal.org/node/1830090
$value = _select_or_other_options_form_to_storage($value);
form_set_value($element, $value, $form_state);
$form_state['clicked_button']['#post'][$element['#name']] = $value; // Is this something we should do?
}
else {
form_set_value($element, array(array('value' => '')), $form_state);
}
// Add values to available options is configured to do so.
if (!empty($form_state['field'][$field_name][$element['#parents'][1]]['instance'])) {
$instance = $form_state['field'][$field_name][$element['#parents'][1]]['instance'];
}
else {
$instance = field_widget_instance($element, $form_state);
}
if ($instance['widget']['settings']['other_unknown_defaults'] == 'available') {
if (
($element['select']['#value'] == 'select_or_other' || (is_array($element['select']['#value']) && isset($element['select']['#value']['select_or_other']))) &&
!empty($element['other']['#value']) &&
!isset($element['#options'][$element['other']['#value']])
) {
// Get the latest instance.
$instance = field_read_instance($instance['entity_type'], $instance['field_name'], $instance['bundle']);
// Make the change.
$instance['widget']['settings']['available_options'] .= "\n" . $element['other']['#value'];
// Save the instance.
field_update_instance($instance);
}
}
//return $element;
}
/**
* Implements hook_field_formatter_info().
*/
function select_or_other_field_formatter_info() {
return array(
'select_or_other_formatter' => array(
'label' => t('Select or other'),
'field types' => array('text', 'number_integer', 'number_decimal', 'number_float'),
),
);
}
/**
* Implements hook_field_formatter_view().
*/
function select_or_other_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
$element = array();
$settings = $display['settings'];
$field_options = array();
if (isset($instance['widget']['settings']['available_options'])) {
$field_options = explode("\n", $instance['widget']['settings']['available_options']);
$pos = strpos($instance['widget']['settings']['available_options'], '|');
if ($pos !== FALSE) {
// There are keys.
foreach ($field_options as $field_item) {
$exploded = explode('|', $field_item);
$temp_options[$exploded[0]] = $exploded[1];
}
$field_options = $temp_options;
}
}
foreach ($items as $delta => $item) {
if (array_key_exists($item['value'], $field_options)) {
$element[$delta] = array('#markup' => $field_options[$item['value']]);
}
else {
$element[$delta] = array('#markup' => $item['value']);
}
}
return $element;
}
/**
* Implements hook_field_widget_error().
*/
function select_or_other_field_widget_error($element, $error, $form, &$form_state) {
form_error($element, $error['message']);
}