You are here

cck_select_other.module in CCK Select Other 7

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',
        '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_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',
        'list_boolean',
      ),
    ),
  );
}

/**
 * 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);
  $def = $instance['required'] ? '' : '_none';
  $otherdef = '';
  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'] : '';
    }
  }

  // This needs to be pulled out of our original element.
  $description = $element['#description'];
  $element = array(
    '#type' => $instance['widget']['type'],
    '#default_value' => '',
    '#prefix' => '<div id="field-' . substr($field['field_name'], 6) . '-' . $langcode . '-' . $delta . '-wrapper">',
    '#suffix' => '</div>',
    '#field_name' => $field['field_name'],
    // Required fields for field_conditional_state.
    '#field_parents' => isset($form['#parents']) ? $form['#parents'] : array(),
    '#bundle' => $instance['bundle'],
  );
  $element['select_other_list'] = array(
    '#title' => check_plain($instance['label']),
    '#description' => !empty($description) ? $description : t('You may specify your own option.'),
    '#type' => 'select',
    '#options' => $options,
    '#default_value' => $def,
    '#attributes' => array(
      'class' => array(
        'form-text form-select select_other_list',
      ),
    ),
    '#required' => $instance['required'],
  );

  // @fixme - #states is REALLY slow here so I'm using my own shit again.
  $element['select_other_text_input'] = array(
    '#type' => 'textfield',
    '#default_value' => $otherdef,
    '#attributes' => array(
      'class' => array(
        'form-text 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) {
  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) {

  // Reverse element parents because of element containers, notably profile2.
  $reversed = array_reverse($element['#parents']);
  $element_name = array_shift($reversed);
  $delta = array_shift($reversed);
  $langcode = array_shift($reversed);
  $field_name = array_shift($reversed);
  if (isset($form_state['field'][$field_name])) {

    // Retrieve stored field & instance info and form state values.
    $field = $form_state['field'][$field_name];
    $values =& $form_state['values'];
  }
  elseif (!empty($reversed) && isset($form_state['field']['#parents'])) {

    // Profile 2 exception.
    $container = array_shift($reversed);
    $field = $form_state['field']['#parents'][$container]['#fields'][$field_name];
    $values =& $form_state['values'][$container];
  }
  else {

    // Catastrophic error..?
    form_set_error($element['#name'], t('An error occurred trying to validate this field.'));
    watchdog('cck_select_other', 'Could not find field info in form state array for select other field, %name.', array(
      '%name' => $field_name,
    ), WATCHDOG_ERROR);
  }
  if ($field[$langcode]['instance']['required'] && $values[$field_name][$langcode][$delta]['select_other_list'] == 'other' && empty($values[$field_name][$langcode][$delta]['select_other_text_input'])) {

    // Empty other field.
    form_set_error($element['#name'], t('A non-empty value is required for this option.'));
  }
  if (!$field[$langcode]['instance']['required'] && $values[$field_name][$langcode][$delta]['select_other_list'] == '_none') {

    // Non-required field value.
    form_set_value($element, array(
      NULL,
    ), $form_state);
  }
}

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

/**
 * 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;
  }
  $keys = $element['#parents'];

  // field_values need to be a reference!
  $field_values =& $form_state['values'];
  foreach ($keys as $key) {
    $field_values =& $field_values[$key];
  }

  // Reverse array parents because of element containers, notably 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'] == '_none') {

      // If we are not a required field, then we do not set a value.
      $element['#value'] = '';
      $field_values = array(
        'value' => '',
      );
    }
    else {
      if ($field_values['select_other_list'] == 'other') {

        // Use text input if we have 'other' selected
        $element['#value'] = $field_values['select_other_text_input'];
        $field_values = array(
          'value' => $field_values['select_other_text_input'],
        );
      }
      else {

        // Use the select list otherwise
        $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;
  $errors = form_get_errors();
  if (!empty($errors)) {

    // Validation errors for the text input box get lost so need to be injected.
    $text_element = $element['#name'] . '[select_other_text_input]';
    if (in_array($text_element, array_keys($errors))) {
      $element['select_other_text_input']['#attributes']['class'][] = 'error';
    }
  }
  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',
  );
}

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_element_info Implementation of hook_element_info().
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_post_render Post-render callback to add javascript functionality
cck_select_other_pre_render Pre render callback for the form so we can recreate the fake form after it gets blown away by the CCK process callback.
cck_select_other_process CCK Select Other widget process callback
cck_select_other_views_api Implementation of hook_views_api().
cck_select_other_widget_validate Validate empty text input for other selection.