You are here

select_or_other.field_widget.inc in Select (or other) 7.3

Same filename and directory in other branches
  1. 7.2 select_or_other.field_widget.inc

The Select (or other) field widget.

File

select_or_other.field_widget.inc
View source
<?php

/**
 * @file
 * The Select (or other) field widget.
 */

/**
 * Implements hook_field_widget_info().
 */
function select_or_other_field_widget_info() {
  $field_types = array(
    'text',
    'number_integer',
    'number_decimal',
    'number_float',
  );
  $settings = array(
    'available_options' => '',
    'other' => t('Other'),
    'other_title' => '',
    'other_unknown_new_option' => FALSE,
    'other_unknown_defaults' => 'other',
    'other_size' => 60,
  );
  $defaults = select_or_other_field_widget_defaults($field_types, $settings);
  return array(
    'select_or_other' => array(
      'label' => t('Select (or other) list'),
    ) + $defaults,
    'select_or_other_buttons' => array(
      'label' => t('Select (or other) check boxes/radio buttons'),
    ) + $defaults,
  );
}

/**
 * Implements hook_field_widget_settings_form().
 */
function select_or_other_field_widget_settings_form($field, $instance) {
  $form = array();
  $settings =& $instance['widget']['settings'];
  $provided_available_options = _select_or_other_invoke_available_options($instance);
  $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_widget_settings_form_validate',
    ),
  );
  if ($provided_available_options) {
    $form['provided_available_options'] = array(
      '#type' => 'textarea',
      '#disabled' => TRUE,
      '#title' => t('Module provided Available options'),
      '#description' => t('A list of values that are, by default, available for selection. These are provided by a module and not editable. Displayed in 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.'),
      '#value' => implode("\r\n", $provided_available_options),
    );
  }
  $form = $form + select_or_other_field_widget_other_options($settings);
  $form['other_size'] = array(
    '#type' => 'textfield',
    '#title' => t('<em>Other</em> field size'),
    '#default_value' => $settings['other_size'],
    '#required' => TRUE,
    '#element_validate' => array(
      'element_validate_integer_positive',
    ),
  );
  $form['other_unknown_new_option'] = array(
    '#type' => 'checkbox',
    '#title' => t("Add 'Other' value to the list of choices for this field."),
    '#default_value' => $settings['other_unknown_new_option'],
  );
  $form['other_unknown_defaults'] = array(
    '#type' => 'radios',
    '#title' => t("Behaviour of 'Other' value when editing existing content."),
    '#description' => t("What should happen if the 'Other' option was used to enter a new value in this field and the user later visits the edit form?"),
    '#options' => array(
      'other' => t('Add the values to the other field'),
      'append' => t('Add the values to the list of choices for the specific entity (i.e. node)'),
    ),
    '#default_value' => isset($settings['other_unknown_defaults']) ? $settings['other_unknown_defaults'] : 'other',
    '#required' => TRUE,
  );
  $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,
  );
  return $form;
}

/**
 * Validation callback for select_or_other_widget_settings fields.
 */
function select_or_other_widget_settings_form_validate($element, &$form_state) {

  // Remove empty lines to prevent notices.
  if ($element['#name'] === 'instance[widget][settings][available_options]') {
    $values = explode("\n", $element['#value']);
    foreach ($values as $key => $value) {
      if (empty($value)) {
        unset($values[$key]);
      }
    }
    form_set_value($element, implode("\n", $values), $form_state);
  }
}

/**
 * Implements hook_field_widget_form().
 */
function select_or_other_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
  $default_value = array();
  foreach ($items as $delta => $item) {
    if (isset($item['value'])) {
      $default_value[$delta] = $item['value'];
    }
  }

  // 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' => $field['cardinality'] != 1 ? $default_value : reset($default_value),
    '#options' => select_or_other_field_widget_form_prepare_options($field, $instance),
    '#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'],
  );

  // Set the empty option on single option fields where applicable.
  if ($field['cardinality'] == 1) {
    if (strpos($instance['widget']['type'], 'buttons') === FALSE) {

      // We are dealing with a select element.
      if (empty($element['#default_value']) && $element['#required']) {
        $element['#empty_option'] = t('- Select -');
      }
      elseif (!$element['#required']) {
        $element['#empty_option'] = t('- None -');
      }
    }
    else {

      // This is a radios element.
      if (!empty($element['#default_value']) && !$element['#required']) {
        $element['#options'] = array(
          '' => t('- None -'),
        ) + $element['#options'];
      }
    }
  }
  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_buttons':
      $element['#select_type'] = $field['cardinality'] == 1 ? 'radios' : 'checkboxes';
      break;
  }
  return $element;
}

/**
 * Prepare options for the widget list.
 */
function select_or_other_field_widget_form_prepare_options($field, $instance) {
  $options = array();
  $list = _select_or_other_field_widget_get_available_options($instance, $field);
  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);
        $options[$key] = $optgroup_options;
      }
    }
    else {
      select_or_other_field_widget_form_prepare_option($options, $key, $opt);
    }
  }

  // @todo: This isset() can probably be taken out in drupal 8.
  if (isset($settings['sort_options']) && $settings['sort_options']) {
    natcasesort($options);
  }
  return $options;
}

/**
 * Prepare a single option.
 */
function select_or_other_field_widget_form_prepare_option(&$options, $key, $opt) {
  $opt = trim($opt);
  if (empty($opt)) {
    return;
  }

  // Sanitize the user input with a permissive filter
  // and convert special characters.
  $opt = html_entity_decode(filter_xss($opt));

  // If option has a key specified
  if (strpos($opt, '|') !== FALSE) {
    list($key, $value) = explode('|', $opt);
    $options[$key] = isset($value) && $value !== '' ? html_entity_decode($value) : $key;
  }
  else {
    $options[$opt] = html_entity_decode($opt);
  }
}

/**
 * Element validate callback for a Select (or other) field widget.
 */
function select_or_other_field_widget_validate($element, &$form_state) {
  $field_name = $element['#field_name'];
  $field_info = field_info_field($field_name);
  $value = select_or_other_fetch_value($element);
  if ($value !== "") {

    // Filter out 'none' value (if present, will always be in key 0)
    if (isset($items[0]['value']) && $items[0]['value'] === '') {
      unset($items[0]);
    }
    $delta = 0;
    $values = array();
    foreach ((array) $value as $v) {
      if ($field_info['type'] == 'number_integer' && !preg_match('/^-?\\d+$/', $v)) {
        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;
    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.
  $instance = field_widget_instance($element, $form_state);
  if (isset($instance['widget']['settings']['other_unknown_new_option']) && $instance['widget']['settings']['other_unknown_new_option']) {
    $other_value = _fetch_other_value($element);
    $append = array();
    if (!empty($other_value)) {
      foreach ($other_value as $key => $value) {
        if (!isset($element['#options'][$key])) {
          $append[] = $value;
        }
      }
    }
    if (!empty($append)) {

      // 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" . implode("\n", $append);

      // Save the instance.
      field_update_instance($instance);
    }
  }
}

/**
 * Implements hook_field_widget_error().
 */
function select_or_other_field_widget_error($element, $error, $form, &$form_state) {
  form_error($element, $error['message']);
}

/**
 * Helper function to get a list of options from the settings.
 * @param $instance
 *  Instance settings array.
 * @param $field
 *  Field info array.
 * @return array
 *  Available options.
 */
function _select_or_other_field_widget_get_available_options($instance, $field) {

  // TODO refactor to be able to remove this function.
  $options = array();
  if ($field['type'] !== 'taxonomy_term_reference') {
    $options = _select_or_other_invoke_available_options($instance);
    $options = array_merge($options, explode("\n", $instance['widget']['settings']['available_options']));
  }
  return $options;
}

/**
 * Helper function which invokes hook_select_or_other_available_options.
 *
 * @param $instance
 *  Field instance array.
 * @return array
 *  Available options.
 */
function _select_or_other_invoke_available_options($instance) {
  $entity_type = $instance['entity_type'];
  $bundle = $instance['bundle'];
  $field_name = $instance['field_name'];
  $available_options = module_invoke_all('select_or_other_available_options', $entity_type, $bundle, $field_name);
  foreach ($available_options as $key => $value) {
    if (!strpos($value, '|')) {
      $available_options[$key] = "{$value}|{$value}";
    }
  }
  return $available_options;
}
function select_or_other_fetch_value($element) {
  $value = $element['select']['#value'];
  if (is_array($value) && isset($value['select_or_other'])) {

    // This is a multiselect which uses associated arrays.
    unset($value['select_or_other']);
    $value += _fetch_other_value($element);
  }
  elseif ($value === 'select_or_other') {

    // This is a single select which uses strings.
    $other_value = _fetch_other_value($element, FALSE);
    if (!$other_value) {
      form_error($element['other'], t('!name: !title is required', array(
        '!name' => t($element['select']['#title']),
        '!title' => $element['#other'],
      )));
    }
    else {
    }
    $value = $other_value;
  }
  return $value;
}

/**
 * Helper function to retrieve the other value from an element.
 */
function _fetch_other_value($element, $return_array = TRUE) {
  $other_value = array();
  if ($element['other']['#value']) {

    // If the value in the other element was already a valid option, we want to
    // store that value instead of the one in the other field.
    foreach ($element['#options'] as $key => $option) {
      if ($element['other']['#value'] === $option) {
        $other_value[$key] = $option;
        break;
      }
    }
    if (!$other_value) {
      $other_value[$element['other']['#value']] = $element['other']['#value'];
    }
  }
  if ($return_array) {
    return $other_value;
  }
  else {
    $keys = array_keys($other_value);
    return array_pop($keys);
  }
}

/**
 * Helper function to get the default settings for select or other widgets.
 *
 * @param array $field_types
 *   Field types this widget is available for.
 * @param $settings
 *   Default widget settings.
 *
 * @return array
 *   Associated array to be used in hook_field_widget_info().
 */
function select_or_other_field_widget_defaults($field_types, $settings) {
  return array(
    'field types' => $field_types,
    'behaviors' => array(
      'multiple values' => FIELD_BEHAVIOR_CUSTOM,
      'default value' => FIELD_BEHAVIOR_DEFAULT,
    ),
    'settings' => $settings,
    'weight' => 2,
  );
}

/**
 * Creates part of the field_widget_settings_form.
 *
 * @param array $settings
 *   Default values to be used in the form.
 */
function select_or_other_field_widget_other_options($settings) {
  $form['other'] = array(
    '#type' => 'textfield',
    '#title' => t('<em>Other</em> option'),
    '#description' => t('Label for the option that the user will choose when they want to supply an <em>other</em> value.'),
    '#default_value' => isset($settings['other']) ? $settings['other'] : t('Other'),
    '#required' => TRUE,
  );
  $form['other_title'] = array(
    '#type' => 'textfield',
    '#title' => t('<em>Other</em> field title'),
    '#description' => t('Label for the field in which the user will supply an <em>other</em> value.'),
    '#default_value' => isset($settings['other_title']) ? $settings['other_title'] : '',
  );
  $form['other_size'] = array(
    '#type' => 'textfield',
    '#title' => t('<em>Other</em> field size'),
    '#default_value' => $settings['other_size'],
    '#required' => TRUE,
    '#element_validate' => array(
      'element_validate_integer_positive',
    ),
  );
  return $form;
}

Functions

Namesort descending Description
select_or_other_fetch_value
select_or_other_field_widget_defaults Helper function to get the default settings for select or other widgets.
select_or_other_field_widget_error Implements hook_field_widget_error().
select_or_other_field_widget_form Implements hook_field_widget_form().
select_or_other_field_widget_form_prepare_option Prepare a single option.
select_or_other_field_widget_form_prepare_options Prepare options for the widget list.
select_or_other_field_widget_info Implements hook_field_widget_info().
select_or_other_field_widget_other_options Creates part of the field_widget_settings_form.
select_or_other_field_widget_settings_form Implements hook_field_widget_settings_form().
select_or_other_field_widget_validate Element validate callback for a Select (or other) field widget.
select_or_other_widget_settings_form_validate Validation callback for select_or_other_widget_settings fields.
_fetch_other_value Helper function to retrieve the other value from an element.
_select_or_other_field_widget_get_available_options Helper function to get a list of options from the settings.
_select_or_other_invoke_available_options Helper function which invokes hook_select_or_other_available_options.