You are here

cck_select_other.module in CCK Select Other 7.2

Implements a select list widget that lets a user provide an alternate option.

File

cck_select_other.module
View source
<?php

/**
 * @file
 * Implements a select list widget that lets a user provide an alternate option.
 */

/**
 * Implementation of hook_field_widget_info().
 */
function cck_select_other_field_widget_info() {
  return array(
    'cck_select_other' => 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_integer',
        'list_float',
        '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_field_formatter_info().
 */
function cck_select_other_field_formatter_info() {
  return array(
    'cck_select_other' => array(
      'label' => t('Select other'),
      'description' => t('The default list module formatters do not take into account select other list widgets.'),
      'field types' => array(
        'list_integer',
        'list_float',
        'list_text',
      ),
    ),
  );
}

/**
 * Implementation of hook_field_formatter_view().
 */
function cck_select_other_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  if ($display['type'] == 'cck_select_other') {

    // Only format for the cck_select_other display (just in case).
    $settings = $instance['widget']['settings'];
    $options = cck_select_other_options($instance);
    $element = array();
    foreach ($items as $delta => $item) {
      $value = isset($options[$item['value']]) ? field_filter_xss($options[$item['value']]) : field_filter_xss($item['value']);
      $element[$delta] = array(
        '#markup' => $value,
      );
    }
    return $element;
  }
}

/**
 * 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 &lt;?php ?&gt; 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);

  // Setup select other wrapper.
  $element += array(
    '#bundle' => $instance['bundle'],
    '#field_name' => $field['field_name'],
    '#langcode' => $langcode,
    '#element_validate' => array(
      'cck_select_other_widget_validate',
    ),
    '#pre_render' => array(
      'cck_select_other_widget_pre_render',
    ),
    '#attributes' => array(
      'class' => array(
        'form-select-other-wrapper',
        'cck-select-other-wrapper',
      ),
    ),
  );

  // Setup select list.
  $element['select_other_list'] = array(
    '#title' => $element['#title'],
    '#description' => $element['#description'],
    '#type' => 'select',
    '#options' => $options,
    '#required' => $instance['required'],
    '#attributes' => array(
      'class' => array(
        'form-text form-select form-select-other-list',
      ),
    ),
  );

  // Setup text input.
  $element['select_other_text_input'] = array(
    '#type' => 'textfield',
    '#title' => t('Provide other option'),
    '#title_display' => 'invisible',
    '#size' => 60,
    '#attributes' => array(
      'class' => array(
        'form-text form-select-other-text-input',
      ),
    ),
  );

  // Default empty values.
  $list_default = $instance['required'] ? '' : '_none';
  $text_default = '';
  $value = '';
  if (isset($items[$delta]['value'])) {

    // Use the value provided in items.
    $value = $items[$delta]['value'];
  }
  elseif (isset($instance['default_value'])) {

    // Use the default value of the field if it is set.
    $value = $instance['default_value'][0]['value'];
  }
  if ($value && in_array($value, array_keys($options))) {

    // Value is not empty and value is in the list.
    $list_default = $value;
  }
  elseif ($value) {

    // Set the list default to other.
    $list_default = 'other';
    $text_default = $value;
  }

  // Set default values.
  $element['select_other_list']['#default_value'] = $list_default;
  $element['select_other_text_input']['#default_value'] = $text_default;
  return $element;
}

/**
 * Implementation of hook_form_alter().
 */
function cck_select_other_form_alter(&$form, &$form_state, $form_id) {
  if ($form_id == 'field_ui_field_edit_form' && isset($form_state['build_info']['args'][0]['widget']) && $form_state['build_info']['args'][0]['widget']['type'] == 'cck_select_other' && isset($form['field']['settings']['allowed_values']) && isset($form['widget']['settings']['allowed_values'])) {

    // If the field is already created, just remove the validation callback.
    // @todo fix Drupal core to not require a field validation function or make it alterable.
    $form['field']['settings']['allowed_values']['#element_validate'] = NULL;
  }
  else {
    if ($form_id == 'field_ui_field_settings_form' && isset($form_state['build_info']['args'][0]['widget']) && $form_state['build_info']['args'][0]['widget']['type'] == 'cck_select_other') {

      // If the field is new, then we need to provide some better feedback to theh user about this.
      $form['field']['settings']['allowed_values']['#prefix'] = '<div class="messages warning">' . t('Select other lists do not require allowed values. You will be prompted to provide select list options after you save field settings. <strong>It is highly recommended that you do not provide allowed values during this step.</strong>') . '</div>';
    }
  }
}

/**
 * Validate empty text input for other selection.
 */
function cck_select_other_widget_validate($element, &$form_state) {
  $values = drupal_array_get_nested_value($form_state['values'], $element['#array_parents']);
  if (empty($values)) {

    // Field UI does not behave in the same way as normal form operations, and
    // values should be extracted from $element['#parents'] instead.
    $values = drupal_array_get_nested_value($form_state['values'], $element['#parents']);
  }
  if (!$element['select_other_list']['#required'] && $values['select_other_list'] == '_none') {

    // Empty select list option.
    form_set_value($element, array(
      'value' => NULL,
    ), $form_state);
  }
  elseif ($element['select_other_list']['#required'] && $values['select_other_list'] == '') {

    // Empty select list option for required field.
    form_set_value($element, array(
      'value' => '',
    ), $form_state);
    form_error($element, t('You must select an option.'));
  }
  elseif ($element['select_other_list']['#required'] && $values['select_other_list'] == 'other' && !$values['select_other_text_input']) {

    // Empty text input for required field.
    form_set_value($element, array(
      'value' => NULL,
    ), $form_state);
    form_error($element['select_other_text_input'], t('You must provide a value for this option.'));
  }
  elseif ($values['select_other_list'] == 'other' && $values['select_other_text_input']) {

    // Non-empty text input value.
    form_set_value($element, array(
      'value' => $values['select_other_text_input'],
    ), $form_state);
  }
  elseif ($values['select_other_list'] == 'other' && !$values['select_other_text_input']) {

    // Empty text for non-required field.
    form_set_value($element, array(
      'value' => NULL,
    ), $form_state);
  }
  else {

    // Non-empty select list value.
    form_set_value($element, array(
      'value' => $values['select_other_list'],
    ), $form_state);
  }
  $field = field_info_field($element['#field_name']);

  // Validate integer and float values for other options.
  if ($field['type'] == 'list_integer' && $values['select_other_list'] == 'other') {
    if (!preg_match('/^-?\\d+$/', $values['select_other_text_input'])) {
      form_error($element['select_other_text_input'], t('Only integers are allowed.'));
    }
  }
  elseif ($field['type'] == 'list_float' && $values['select_other_list'] == 'other') {
    if (!is_numeric($values['select_other_text_input'])) {
      form_error($element['select_other_text_input'], t('Only valid numbers are allowed.'));
    }
  }
}

/**
 * Attaches Javascript during pre build because this is when array parents
 * should be defined to take advantage of modules that alter the element
 * structure such as field_collection.
 *
 * @param $element
 *   The element array.
 * @return array
 *   The element array.
 */
function cck_select_other_widget_pre_render($element) {
  $key = $element['#field_name'] . '_' . $element['#delta'];
  $settings = array(
    $key => array(
      'list_element' => $element['select_other_list']['#id'],
      'input_element' => $element['select_other_text_input']['#id'],
    ),
  );
  $element['#attached'] = array(
    'js' => array(
      array(
        'data' => array(
          'CCKSelectOther' => $settings,
        ),
        'type' => 'setting',
      ),
      drupal_get_path('module', 'cck_select_other') . '/cck_select_other.js',
    ),
  );
  return $element;
}

/**
 * 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');
  }
  if (!$field['required']) {
    $options = array(
      '_none' => t('- None - '),
    ) + $options;
  }
  else {
    $options = array(
      '' => t('- Select a value -'),
    ) + $options;
  }
  return $options;
}

/**
 * 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',
  );
}

Functions

Namesort descending Description
cck_select_other_content_migrate_field_alter Implementation of hook_content_migrate_field_alter().
cck_select_other_content_migrate_instance_alter Implementation of hook_content_migrate_instance_alter().
cck_select_other_field_formatter_info Implementation of hook_field_formatter_info().
cck_select_other_field_formatter_view Implementation of hook_field_formatter_view().
cck_select_other_field_widget_form Implementation of hook_field_widget_form().
cck_select_other_field_widget_info Implementation of hook_field_widget_info().
cck_select_other_field_widget_settings_form Implementation of hook_field_widget_settings_form().
cck_select_other_form_alter Implementation of hook_form_alter().
cck_select_other_options Retrieve options for the select list
cck_select_other_views_api Implementation of hook_views_api().
cck_select_other_widget_pre_render Attaches Javascript during pre build because this is when array parents should be defined to take advantage of modules that alter the element structure such as field_collection.
cck_select_other_widget_validate Validate empty text input for other selection.