array( 'label' => t('Select other list'), 'description' => t('Provides an "other" option, which allows the user to provide an alternate value.'), 'field types' => array('list', 'list_number', 'list_text'), 'settings' => array( 'select_list_options' => '', 'select_list_options_fieldset' => array( 'advanced_options' => array( 'select_list_options_php' => '', ), ), ), 'behaviors' => array( 'default value' => FIELD_BEHAVIOR_DEFAULT, ), ), ); } /** * Implementation of hook_element_info(). */ function cck_select_other_element_info() { return array( 'cck_select_other' => array( '#input' => TRUE, '#process' => array('cck_select_other_process'), '#post_render' => array('cck_select_other_post_render'), '#pre_render' => array('cck_select_other_pre_render'), ), ); } /** * Implementation of hook_field_widget_settings_form(). */ function cck_select_other_field_widget_settings_form($field, $instance) { $widget = $instance['widget']; $settings = $widget['settings']; $form['select_list_options'] = array( '#type' => 'textarea', '#title' => t('Select list options'), '#description' => t('CCK Select Other uses a separate text area to generate options. You may also put restricted values in the Allowed Values text area.'), '#default_value' => (!empty($settings['select_list_options'])) ? $settings['select_list_options'] : 'other|Other', ); $form['select_list_options_fieldset']['advanced_options'] = array( '#type' => 'fieldset', '#title' => t('PHP code'), '#collapsible' => TRUE, '#collapsed' => empty($settings['select_list_options_fieldset']['advanced_options']['select_list_options_php']), ); $form['select_list_options_fieldset']['advanced_options']['select_list_options_php'] = array( '#type' => 'textarea', '#title' => t('Code'), '#default_value' => !empty($settings['select_list_options_fieldset']['advanced_options']['select_list_options_php']) ? $settings['select_list_options_fieldset']['advanced_options']['select_list_options_php'] : '', '#rows' => 6, '#description' => t('Advanced usage only: PHP code that returns a keyed array of proposed select list options. Should not include <?php ?> delimiters. If this field is filled out, the array returned by this code will override the proposed select list options above.'), ); if (!user_access('use PHP for settings')) { $form['select_list_options_fieldset']['advanced_options']['select_list_options_php']['#disabled'] = TRUE; $form['select_list_options_fieldset']['advanced_options']['select_list_options_php']['#prefix'] = t('You do not have access to write PHP code to generate select list options.'); } return $form; } /** * Implementation of hook_field_widget_form(). */ function cck_select_other_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) { $options = cck_select_other_options($instance); $def = ''; if (empty($items)) { $items[] = array('value' => ''); } if (!isset($items[$delta])) { $items[$delta] = array('value' => ''); } // Set default value if we have instance data for delta, a default // value setting, or something basic if none at all. if (!empty($items[$delta]['value'])) { $def = (in_array($items[$delta]['value'], array_keys($options))) ? $items[$delta]['value'] : 'other'; $otherdef = ($def == 'other') ? $items[$delta]['value'] : ''; } else if (isset($instance['default_value'])) { $def = isset($instance['default_value'][0]['select_other_list']) ? $instance['default_value'][0]['select_other_list'] : 'other'; $otherdef = ($def == 'other') ? $instance['default_value'][0]['value'] : ''; } else { $def = 'other'; $otherdef = ''; } $element = array( '#type' => $instance['widget']['type'], '#default_value' => '', '#prefix' => '
', '#suffix' => '
', '#field_name' => $field['field_name'], // Required fields for field_conditional_state. '#field_parents' => $form['#parents'], '#bundle' => $instance['bundle'], ); $element['select_other_list'] = array( '#title' => t('%label', array('%label' => $instance['label'])), '#description' => t('You may specify your own option.'), '#type' => 'select', '#options' => $options, '#default_value' => check_plain($def), '#attributes' => array( 'class' => array('form-text form-select select_other_list'), ), ); // @fixme - #states is REALLY slow here so I'm using my own shit again. $element['select_other_text_input'] = array( '#type' => 'textfield', '#default_value' => check_plain($otherdef), '#attributes' => array( 'class' => array('form-text select_other_text_input'), ), ); if ($instance['required']) { $element['select_other_text_input']['#element_validate'] = array('cck_select_other_widget_validate'); } return $element; } /** * Implementation of hook_form_alter(). */ function cck_select_other_form_alter(&$form, &$form_state, $form_id) { // @todo fix Drupal core field bug so that field validation functions are alterable. if ( $form_id == 'field_ui_field_edit_form' && isset($form['field']['settings']['allowed_values']) && isset($form['widget']['settings']['allowed_values']) ) { $form['field']['settings']['allowed_values']['#element_validate'] = NULL; } } /** * Validate empty text input for other selection. */ function cck_select_other_widget_validate($element, &$form_state) { $field_name = $element['#parents'][0]; $langcode = $element['#parents'][1]; $delta = $element['#parents'][2]; if ($form_state['values'][$field_name][$langcode][$delta]['select_other_list'] == 'other' && empty($form_state['values'][$field_name][$langcode]['select_other_text_input'])) { form_set_error($field_name . '[' . $langcode . '][' . $delta . '][select_other_text_input]', t('A non-empty value is required for this option.')); } } /** * Retrieve options for the select list * @param $field the field instance we're working with * @return an array of options to pass into the Form API. */ function cck_select_other_options($field) { if (!isset($field['widget'])) { return array(); } $options = eval($field['widget']['settings']['select_list_options_fieldset']['advanced_options']['select_list_options_php']); if (empty($options)) { $options_str = $field['widget']['settings']['select_list_options']; if (!empty($options_str)) { $options_arr = preg_split("/[\r]?[\n]/", $options_str); if (count($options_arr) > 0) { foreach ($options_arr as $option_str) { $option_arr = preg_split("/\|/", $option_str); if (count($option_arr) == 2) { $options[check_plain($option_arr[0])] = t('@option', array('@option' => $option_arr[1])); } else { $options[check_plain($option_arr[0])] = t('@option', array('@option' => $option_arr[0])); } } } } } else { foreach ($options as $key => $option) { if (!is_numeric($key)) { $key = check_plain($key); } $options[$key] = t('@option', array('@option' => $option)); } } if (!isset($options['other'])) { $options['other'] = t('Other'); } return $options; } /** * CCK Select Other widget process callback * @param $element * @param &$form_state * @return $element; */ function cck_select_other_process($element, &$form_state) { if (!isset($element['#name'])) { return $element; } // No matches = not our field. $n = preg_match_all("/[A-Za-z0-9\-\_]+/", $element['#name'], $matches); if ($n == 0) { return $element; } // By any chance if we don't have any array keys, get out of here. $keys = isset($matches[0]) ? $matches[0]: NULL; if (!isset($keys)) { return $element; } // field_values need to be a reference! $field_values = &$form_state['values']; foreach ($keys as $key) { $field_values = &$field_values[$key]; } // We have to reverse the array keys because of element containers (profile2). $reversed = array_reverse($keys); $delta = $reversed[0]; $langcode = $reversed[1]; $field_name = $reversed[2]; if (isset($field_values) && !empty($field_values)) { if ($field_values['select_other_list'] == 'other') { $element['#value'] = $field_values['select_other_text_input']; $field_values = array( 'value' => $field_values['select_other_text_input'], ); // Validate empty? This seems to be done in list.module in Drupal 7 now. } else { $element['#value'] = $field_values['select_other_list']; $field_values = array( 'value' => $field_values['select_other_list'], ); } return $element; } else { $element['#value'] = ''; if (isset($element['select_other_list']['#default_value'])) { $element['select_other_list']['#value'] = $element['select_other_list']['#default_value']; } } return $element; } /** * Pre render callback for the form so we can recreate the fake form after it gets * blown away by the CCK process callback. * @param $element the element * @param $form_state * @return $form */ function cck_select_other_pre_render($element, $form_state = NULL) { static $js; if (!isset($form_state)) { return $element; } if (!isset($element['#type']) || $element['#type'] <> 'cck_select_other') { return $element; } // No matches = not our field. $n = preg_match_all("/[A-Za-z0-9\-\_]+/", $element['#name'], $matches); if ($n == 0) { return $element; } // By any chance if we don't have any array keys, get out of here. $keys = isset($matches[0]) ? $matches[0]: NULL; if (!isset($keys)) { return $element; } foreach ($keys as $key => $val) { $keys[$key] = preg_replace("/_/", '-', $val); } $field_id = implode('-', $keys); if (!$js) { drupal_add_js(drupal_get_path('module', 'cck_select_other') . '/cck_select_other.js'); $js = TRUE; } drupal_add_js(array('CCKSelectOther' => array(array('field_id' => $field_id))), array('type' => 'setting')); } /** * Post-render callback to add javascript functionality * @param $content * @param $element * @return $form */ function cck_select_other_post_render($content, $element) { static $js; if ($element['#type'] <> 'cck_select_other') { return $content; } // No matches = not our field. $n = preg_match_all("/[A-Za-z0-9\-\_]+/", $element['#name'], $matches); if ($n == 0) { return $element; } // By any chance if we don't have any array keys, get out of here. $keys = isset($matches[0]) ? $matches[0]: NULL; if (!isset($keys)) { return $element; } foreach ($keys as $key => $val) { $keys[$key] = preg_replace("/_/", '-', $val); } $field_id = implode('-', $keys); if (!$js) { drupal_add_js(drupal_get_path('module', 'cck_select_other') . '/cck_select_other.js'); $js = TRUE; } drupal_add_js(array('CCKSelectOther' => array(array('field_id' => $field_id))), array('type' => 'setting')); return $content; } /** * Implementation of hook_content_migrate_field_alter(). */ function cck_select_other_content_migrate_field_alter(&$field_value) { if ($field_value['type'] == 'cck_select_other') { $field_value['type'] = 'list_text'; $field_value['module'] = 'list'; } } /** * Implementation of hook_content_migrate_instance_alter(). */ function cck_select_other_content_migrate_instance_alter(&$instance_value) { if ($instance_value['widget']['module'] == 'cck_select_other') { // Yay! We actually don't need to do anything. But I'm going to call this anyway. } } /** * Implementation of hook_views_api(). */ function cck_select_other_views_api() { return array( 'api' => '3', 'path' => drupal_get_path('module', 'cck_select_other') . '/views', ); }