You are here

name.module in Name Field 6

Same filename and directory in other branches
  1. 8 name.module
  2. 7 name.module

Defines an API for displaying and inputing names.

File

name.module
View source
<?php

/**
 * @file
 * Defines an API for displaying and inputing names.
 */

/**
 * Provides a hook into the theming of the field and instance settings, using
 * #pre_render callbacks.
 */
function name_form_content_field_edit_form_alter(&$form, &$form_state) {
  $field = $form['#field'];
  if ($field['module'] != 'name') {
    return;
  }

  // Moves the instance settings into a nicer table.
  if (!isset($form['widget']['instance_settings']['#pre_render'])) {
    $form['widget']['instance_settings']['#pre_render'] = array();
  }
  $form['widget']['instance_settings']['#pre_render'][] = 'name_field_instance_settings_pre_render';
  $form['widget']['instance_settings']['#field'] = $field;

  // Moves the instance settings into a nicer table.
  if (!isset($form['field']['field_settings']['#pre_render'])) {
    $form['field']['field_settings']['#pre_render'] = array();
  }
  $form['field']['field_settings']['#pre_render'][] = 'name_field_settings_pre_render';
  $form['field']['field_settings']['#field'] = $field;
}
function name_field_instance_settings_pre_render($form) {
  module_load_include('inc', 'name', 'name.admin');
  return _name_field_instance_settings_pre_render($form);
}
function name_field_settings_pre_render($form) {
  module_load_include('inc', 'name', 'name.admin');
  return _name_field_settings_pre_render($form);
}

/**
 * This is the main function that formats a name from an array of components.
 *
 * @param array $name_components
 *        A keyed array of name components.
 *        These are: title, given, middle, family, generational and credentials.
 * @param string $format
 *        The string specifying what format to use.
 * @param array $settings
 *        A keyed array of additional parameters to pass into the function.
 *        Includes:
 *          'object' - An object or array.
 *            This object is used for Token module subsitutions.
 *          'type' - A string.
 *            The object identifier: node, user, etc
 */
function name_format($name_components, $format, $settings = array()) {
  module_load_include('inc', 'name', 'includes/name.parser');
  return _name_format($name_components, $format, $settings);
}

/**
 * Handles the initialization of the Name module settings that
 * are stored in the {variables} table.
 */
function name_settings($key = NULL) {
  $settings = variable_get('name_settings', array());
  $settings += array(
    'default_format' => '(((((t+ig)+im)+if)+is)+ic)',
    'sep1' => ' ',
    'sep2' => ', ',
    'sep3' => '',
  );
  if ($key) {
    return $settings[$key];
  }
  return $settings;
}

/**
 * Implementation of hook_token_list().
 */
function name_token_list($type = 'all') {
  if ($type == 'field' || $type == 'all') {
    module_load_include('inc', 'name', 'includes/name.token');
    return _name_token_list($type);
  }
}

/**
 * Implementation of hook_token_values().
 */
function name_token_values($type, $object = NULL) {
  if ($type == 'field') {
    module_load_include('inc', 'name', 'includes/name.token');
    return _name_token_values($type, $object);
  }
}

/**
 * Implementation of hook_menu().
 */
function name_menu() {
  $items = array();

  // Admin menu items
  $items['admin/settings/name'] = array(
    'title' => 'Name',
    'page callback' => 'name_list_custom_formats',
    'description' => 'List custom name formats.',
    'access arguments' => array(
      'administer site configuration',
    ),
    'file' => 'name.admin.inc',
    'type' => MENU_NORMAL_ITEM,
  );
  $items['admin/settings/name/list'] = array(
    'title' => 'Custom formats',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
  $items['admin/settings/name/add'] = array(
    'title' => 'Add custom format',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'name_custom_formats_form',
    ),
    'access arguments' => array(
      'administer site configuration',
    ),
    'file' => 'name.admin.inc',
    'type' => MENU_LOCAL_TASK,
  );
  $items['admin/settings/name/settings'] = array(
    'title' => 'Settings',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'name_admin_settings_form',
    ),
    'description' => 'Administer the Names module and custom name formats.',
    'access arguments' => array(
      'administer site configuration',
    ),
    'file' => 'name.admin.inc',
    'type' => MENU_LOCAL_TASK,
    'weight' => 10,
  );
  $items['admin/settings/name/%'] = array(
    'title' => 'Edit custom format',
    'page callback' => 'name_custom_format_edit',
    'page arguments' => array(
      3,
    ),
    'access arguments' => array(
      'administer site configuration',
    ),
    'file' => 'name.admin.inc',
    'type' => MENU_CALLBACK,
  );
  $items['admin/settings/name/%/delete'] = array(
    'title' => 'Delete custom name format',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'name_custom_format_delete_form',
      3,
    ),
    'access arguments' => array(
      'administer site configuration',
    ),
    'file' => 'name.admin.inc',
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Implement hook_theme().
 */
function name_theme() {
  $theme = array(
    // Themes the FAPI element.
    'name_element' => array(
      'arguments' => array(
        'element' => NULL,
      ),
    ),
    // Provides the help for the recognized characters
    // in the name_format() format parameter.
    'name_format_parameter_help' => array(
      'file' => 'includes/name.parser.inc',
    ),
  );
  if (module_exists('content')) {
    name_additional_content_theme($theme);
  }
  return $theme;
}

/**
 * Loads a list of all user defined formats.
 */
function name_get_custom_formats() {
  static $formats;
  if (!isset($formats)) {
    $formats = array();
    $result = db_query("SELECT * FROM {name_custom_format} ORDER BY name ASC");
    while ($row = db_fetch_array($result)) {
      $formats[$row['ncfid']] = $row;
    }
  }
  return $formats;
}

/**
 * Helper function to generate a list of all defined custom
 * formatting options.
 */
function name_get_custom_format_options() {
  $options = array();
  foreach (name_get_custom_formats() as $wcfid => $row) {
    $options[$row['machine_name']] = $row['name'];
  }
  natcasesort($options);
  return $options;
}
function name_get_format_by_machine_name($machine_name) {
  if ($machine_name == 'default') {
    return name_settings('default_format');
  }
  $sql = "SELECT format FROM {name_custom_format} WHERE machine_name = '%s'";
  return (string) db_result(db_query($sql, $machine_name));
}

/**
 * Static cache to reuse translated name components.
 */
function _name_translations($intersect = NULL) {
  $nt = NULL;
  if (!isset($nt)) {
    $nt = array(
      'title' => t('Title'),
      'given' => t('Given'),
      'middle' => t('Middle name(s)'),
      'family' => t('Family'),
      'generational' => t('Generational'),
      'credentials' => t('Credentials'),
    );
  }
  return empty($intersect) ? $nt : array_intersect_key($nt, $intersect);
}

/**
 * Private helper function to define the formatter types that are available for
 * the CCK and Token modules.
 */
function _name_formatter_output_types() {
  return array(
    'default' => t('Default'),
    'plain' => t('Plain'),
    'raw' => t('Raw'),
  );
}

/**
 * A brigding function to warn users that are using any custom code.
 *
 * TODO: Remove from Drupal 7 and latter branches past 6-1.x.
 */
function name_construct_components() {
  $warning = FALSE;
  if (!$warning) {
    drupal_set_message(t('The function "%func" is do longer available. This code should be refactored to use "name_format".'), 'error');
    $warning = TRUE;
  }
  return '';
}

/* ----------------------- FAPI Element Code ------------------------------------- */

/**
 * Implementation of hook_elements().
 */
function name_elements() {
  $parts = _name_translations();
  $field_info = name_field_info();
  $field_settings = $field_info['name']['field_settings'];
  $instance_settings = $field_info['name']['instance_settings'];
  return array(
    'name_element' => array(
      '#input' => TRUE,
      '#process' => array(
        'name_element_expand',
      ),
      '#element_validate' => array(
        'name_element_validate',
      ),
      '#default_value' => array(
        'title' => '',
        'given' => '',
        'middle' => '',
        'family' => '',
        'generational' => '',
        'credentials' => '',
      ),
      '#minimum_components' => $field_settings['minimum_components'],
      '#inline_css' => $instance_settings['inline_css'],
      '#components' => array(
        'title' => array(
          'type' => $instance_settings['title_field'],
          'title' => $parts['title'],
          'title_display' => 'description',
          'inline_css' => 0,
          'size' => $instance_settings['size']['title'],
          'maxlength' => $field_settings['max_length']['title'],
          'options' => $field_settings['title_options'],
          'autocomplete' => FALSE,
        ),
        'given' => array(
          'type' => 'textfield',
          'title' => $parts['given'],
          'title_display' => 'description',
          'inline_css' => 0,
          'size' => $instance_settings['size']['given'],
          'maxlength' => $field_settings['max_length']['given'],
          'autocomplete' => FALSE,
        ),
        'middle' => array(
          'type' => 'textfield',
          'title' => $parts['middle'],
          'title_display' => 'description',
          'inline_css' => 0,
          'size' => $instance_settings['size']['middle'],
          'maxlength' => $field_settings['max_length']['middle'],
          'autocomplete' => FALSE,
        ),
        'family' => array(
          'type' => 'textfield',
          'title' => $parts['family'],
          'title_display' => 'description',
          'inline_css' => 0,
          'size' => $instance_settings['size']['family'],
          'maxlength' => $field_settings['max_length']['family'],
          'autocomplete' => FALSE,
        ),
        'generational' => array(
          'type' => $instance_settings['generational_field'],
          'title' => $parts['generational'],
          'title_display' => 'description',
          'inline_css' => 0,
          'size' => $instance_settings['size']['generational'],
          'maxlength' => $field_settings['max_length']['generational'],
          'options' => $field_settings['generational_options'],
          'autocomplete' => FALSE,
        ),
        'credentials' => array(
          'type' => 'textfield',
          'title' => $parts['credentials'],
          'title_display' => 'description',
          'inline_css' => 0,
          'size' => $instance_settings['size']['credentials'],
          'maxlength' => $field_settings['max_length']['credentials'],
          'autocomplete' => FALSE,
        ),
      ),
    ),
  );
}

/**
 * Custom theme callback for the name_element.
 */
function theme_name_element($element) {
  return theme('form_element', $element, $element['#children']);
}

/**
 * The #process callback to create the element.
 */
function name_element_expand($element, $edit, &$form_state, $complete_form) {
  $element['#tree'] = TRUE;
  if (empty($element['#value'])) {
    $element['#value'] = array();
  }
  $element['#value'] += array(
    'title' => '',
    'given' => '',
    'middle' => '',
    'family' => '',
    'generational' => '',
    'credentials' => '',
  );
  $components = $element['#components'];
  $min_components = (array) $element['#minimum_components'];
  foreach (_name_translations() as $key => $title) {
    if (!isset($components[$key]['exclude'])) {
      $element[$key] = _name_render_component($components[$key], $key, $element['#value'][$key], isset($min_components[$key]));
      if ($key == 'credentials') {
        $element[$key]['#prefix'] = '<div class="clear-block" style="float: left;">';
        $element[$key]['#suffix'] = '</div>';
      }
      else {
        $element[$key]['#prefix'] = '<div style="float: left; margin-right: 1em;">';
        $element[$key]['#suffix'] = '</div>';
      }
    }
  }

  // Try to prevent wrapping errors without any additional styles.
  $element['#prefix'] = '<div class="clear-block">';
  $element['#suffix'] = '</div>';
  $element['#pre_render'] = array(
    'name_element_pre_render',
  );
  return $element;
}
function _name_render_component($component, $name, $value, $core) {
  $element = array(
    '#attributes' => array(
      'class' => 'name-element name-' . $name . ($core ? ' name-core-component' : ''),
    ),
  );
  if (isset($component['attributes'])) {
    foreach ($component['attributes'] as $key => $attribute) {
      if (!isset($element['#attributes'][$key])) {
        $element['#attributes'][$key] = $attribute;
      }
      else {
        $element['#attributes'][$key] .= ' ' . $attribute;
      }
    }
  }
  $base_attributes = array(
    'type',
    'title',
    'size',
    'maxlength',
  );
  foreach ($base_attributes as $key) {
    $element['#' . $key] = $component[$key];
  }
  $element['#default_value'] = $value;
  if ($component['type'] == 'select') {
    $element['#options'] = $component['options'];
    $element['#size'] = 1;
  }
  elseif (!empty($component['autocomplete'])) {
    $element['#autocomplete_path'] = $component['autocomplete'];
  }

  // Enable the title options.
  $element['#title_display'] = isset($component['title_display']) ? $component['title_display'] : 'description';
  return $element;
}
function name_element_validate($element, &$form_state) {
  $minimum_components = array_filter($element['#minimum_components']);
  $labels = array();
  foreach ($element['#components'] as $key => $component) {
    if (!isset($component['exclude'])) {
      $labels[$key] = $component['title'];
    }
  }
  $item = $element['#value'];
  $empty = name_content_is_empty($item, NULL);
  $item_components = array();
  foreach (_name_translations() as $key => $title) {
    if (isset($labels[$key]) && !empty($item[$key])) {
      $item_components[$key] = $item[$key];
    }
  }
  if (!$empty && count($minimum_components) != count(array_intersect_key($minimum_components, $item_components))) {
    $missing_labels = array_diff_key(array_intersect_key($labels, $minimum_components), $item_components);
    $label = empty($element['#title']) ? empty($element['#label']) ? 'Field' : $element['#label'] : $element['#title'];
    form_error($element[key($missing_labels)], t('%name also requires the following parts: %components.', array(
      '%name' => $label,
      '%components' => implode(', ', $missing_labels),
    )));
  }
  return $element;
}

/**
 * This function forces the error class attribute onto each of the empty element
 * components as required, themes the element and controls the title display.
 *
 * TODO: There is a lot of WTF here. Why does this need such a hack to
 * propagate the error class down? Is the error element name wrong??
 */
function name_element_pre_render($element) {
  $error_element = empty($element['_error_element']['#value']) ? '' : $element['_error_element']['#value'] . '][';
  $error_element .= $element['#name'];
  $errors = (array) form_set_error();
  foreach ($errors as $error_key => $error) {
    if (strpos($error_key, $error_element) === 0) {
      foreach (_name_translations() as $key => $title) {
        if (isset($element[$key]) && empty($element[$key]['#value'])) {
          $element[$key]['#attributes']['class'] .= ' error';
        }
      }
    }
  }

  // Add a wrapper to single fields that have a description to prevent wrapping.
  if (!empty($element['#description']) && !empty($element['#description'])) {
    $field = NULL;
    if (isset($element['#field_name']) && isset($element['#type_name'])) {
      $field = content_fields($element['#field_name'], $element['#type_name']);
    }
    if (!$field || !$field['multiple']) {
      $element['_name'] = array(
        '#prefix' => '<div class="clear-block">',
        '#suffix' => '</div>',
      );
      foreach (_name_translations() as $key => $title) {
        if (isset($element[$key])) {
          $element['_name'][$key] = $element[$key];
          unset($element[$key]);
        }
      }
      $element['_description'] = array(
        '#value' => '<div class="clear-block description">' . $element['#description'] . '</div>',
      );
      unset($element['#description']);
    }
  }
  foreach (_name_translations() as $key => $title) {
    if (isset($element['_name']) && isset($element['_name'][$key])) {
      if (isset($element['_name'][$key]['#title_display'])) {
        if ($element['_name'][$key]['#title_display'] == 'description') {
          $element['_name'][$key]['#description'] = $element['_name'][$key]['#title'];
        }
        if ($element['_name'][$key]['#title_display'] == 'description' || $element['_name'][$key]['#title_display'] == 'none') {
          $element['_name'][$key]['#title'] = NULL;
        }
      }
    }
    elseif (isset($element[$key])) {
      if (isset($element[$key]['#title_display'])) {
        if ($element[$key]['#title_display'] == 'description') {
          $element[$key]['#description'] = $element[$key]['#title'];
        }
        if ($element[$key]['#title_display'] == 'description' || $element[$key]['#title_display'] == 'none') {
          $element[$key]['#title'] = NULL;
        }
      }
    }
  }
  return $element;
}
function name_additional_content_theme(&$theme) {

  // The default CCK formatter. The other theme callbacks
  // are defined dynamically from the database.
  $theme['name_formatter_default'] = array(
    'arguments' => array(
      'element' => NULL,
    ),
  );

  // Additional CCK formatter callbacks.
  $base_info = array(
    'arguments' => array(
      'element' => NULL,
    ),
    'function' => 'theme_name_formatter_default',
  );
  foreach (_name_formatter_output_types() as $type => $title) {
    if ($type != 'default') {
      $theme['name_formatter_' . $type] = $base_info;
    }
    foreach (name_get_custom_format_options() as $machine_name => $name_title) {
      $theme['name_formatter_' . $type . '_' . $machine_name] = $base_info;
    }
  }
}

/**
 * Implementation of hook_field_info().
 */
function name_field_info() {
  return array(
    'name' => array(
      'label' => t('Name'),
      'description' => t('This field stores a users title, given, middle, family name, generational suffix and credentials in the database.'),
      'field_settings' => array(
        // Components used. At least given or family name is required.
        'components' => array(
          'title',
          'given',
          'middle',
          'family',
          'generational',
          'credentials',
        ),
        // Minimal set of components before considered incomplete
        'minimum_components' => array(
          'given',
          'family',
        ),
        'labels' => _name_translations(),
        'max_length' => array(
          'title' => 31,
          'given' => 63,
          'middle' => 127,
          'family' => 63,
          'generational' => 15,
          'credentials' => 255,
        ),
        'title_options' => implode("\n", array(
          t('-- --'),
          t('Mr.'),
          t('Mrs.'),
          t('Miss'),
          t('Ms.'),
          t('Dr.'),
          t('Prof.'),
        )),
        'generational_options' => implode("\n", array(
          t('-- --'),
          t('Jr.'),
          t('Sr.'),
          t('I'),
          t('II'),
          t('III'),
          t('IV'),
          t('V'),
          t('VI'),
          t('VII'),
          t('VIII'),
          t('IX'),
          t('X'),
        )),
        'sort_options' => array(
          'title' => 'title',
          'generational' => 0,
        ),
      ),
      'instance_settings' => array(
        // Possible elements for either (free) text, autocomplete, select.
        'title_field' => 'select',
        'generational_field' => 'select',
        'inline_css' => array(
          'multiplier' => '0.9',
          'unit' => 'em',
        ),
        'size' => array(
          'title' => 6,
          'given' => 20,
          'middle' => 20,
          'family' => 20,
          'generational' => 5,
          'credentials' => 35,
        ),
        'inline_css_enabled' => array(
          'title' => 0,
          'given' => 0,
          'middle' => 0,
          'family' => 0,
          'generational' => 0,
          'credentials' => 0,
        ),
        'title_display' => array(
          'title' => 'description',
          'given' => 'description',
          'middle' => 'description',
          'family' => 'description',
          'generational' => 'description',
          'credentials' => 'description',
        ),
      ),
    ),
  );
}

/**
 * Implementation of hook_field_settings().
 */
function name_field_settings($op, $field) {
  _name_defaults($field, 'field_settings');
  switch ($op) {
    case 'form':
      $form = array(
        'field_settings' => array(
          '#tree' => TRUE,
        ),
      );
      $components = _name_translations();
      $form['field_settings']['components'] = array(
        '#type' => 'checkboxes',
        '#title' => t('Components'),
        '#default_value' => $field['field_settings']['components'],
        '#required' => TRUE,
        '#description' => t('Only selected components will be activated on this field. All non-selected components / component settings will be ignored.'),
        '#options' => $components,
        '#element_validate' => array(
          '_name_field_minimal_component_requirements',
        ),
      );
      $form['field_settings']['minimum_components'] = array(
        '#type' => 'checkboxes',
        '#title' => t('Minimum components'),
        '#default_value' => $field['field_settings']['minimum_components'],
        '#required' => TRUE,
        '#element_validate' => array(
          '_name_field_minimal_component_requirements',
        ),
        '#description' => t('The minimal set of components required before considered the name field to be incomplete.'),
        '#options' => $components,
      );
      $form['field_settings']['labels'] = array();
      $form['field_settings']['max_length'] = array();
      foreach ($components as $key => $title) {
        $form['field_settings']['max_length'][$key] = array(
          '#type' => 'textfield',
          '#title' => t('Maximum length for !title', array(
            '!title' => $title,
          )),
          '#default_value' => $field['field_settings']['max_length'][$key],
          '#required' => FALSE,
          '#size' => 10,
          '#description' => t('The maximum length of the field in characters. This must be between 1 and 255.'),
          '#element_validate' => array(
            '_name_validate_integer_positive',
          ),
        );
        $form['field_settings']['labels'][$key] = array(
          '#type' => 'textfield',
          '#title' => t('Label for !title', array(
            '!title' => $title,
          )),
          '#default_value' => $field['field_settings']['labels'][$key],
          '#required' => TRUE,
        );
      }

      // TODO - Grouping & grouping sort
      // TODO - Allow reverse free tagging back into the vocabulary.
      $title_options = implode("\n", array_filter(explode("\n", $field['field_settings']['title_options'])));
      $form['field_settings']['title_options'] = array(
        '#type' => 'textarea',
        '#title' => t('Title options'),
        '#default_value' => $title_options,
        '#required' => TRUE,
        '#description' => t("Enter one title per line. Prefix a line using '--' to specify a blank value text. For example: '--Please select a title'."),
      );
      $generational_options = implode("\n", array_filter(explode("\n", $field['field_settings']['generational_options'])));
      $form['field_settings']['generational_options'] = array(
        '#type' => 'textarea',
        '#title' => t('Generational options'),
        '#default_value' => $generational_options,
        '#required' => TRUE,
        '#description' => t("Enter one generational suffix option per line. Prefix a line using '--' to specify a blank value text. For example: '----'."),
      );
      if (module_exists('taxonomy')) {

        // Generational suffixes may be also imported from one or more vocabularies using the tag '[vocabulary:xxx]', where xxx is the vocabulary id. Terms that exceed the maximum length of the generational suffix are not added to the options list.
        $form['field_settings']['title_options']['#description'] .= ' ' . t("%label_plural may be also imported from one or more vocabularies using the tag '[vocabulary:xxx]', where xxx is the vocabulary id. Terms that exceed the maximum length of the %label are not added to the options list.", array(
          '%label_plural' => t('Titles'),
          '%label' => t('Title'),
        ));
        $form['field_settings']['generational_options']['#description'] .= ' ' . t("%label_plural may be also imported from one or more vocabularies using the tag '[vocabulary:xxx]', where xxx is the vocabulary id. Terms that exceed the maximum length of the %label are not added to the options list.", array(
          '%label_plural' => t('Generational suffixes'),
          '%label' => t('Generational suffix'),
        ));
      }
      $sort_options = is_array($field['field_settings']['sort_options']) ? $field['field_settings']['sort_options'] : array(
        'title' => 'title',
        'generational' => '',
      );
      $form['field_settings']['sort_options'] = array(
        '#type' => 'checkboxes',
        '#title' => t('Select field sort options'),
        '#default_value' => $sort_options,
        '#description' => t("This enables sorting on the options after the vocabulary terms are added and duplicate values are removed."),
        '#options' => _name_translations(array(
          'title' => '',
          'generational' => '',
        )),
      );
      return $form;
    case 'validate':

      // Validates options against the title / generational sizes.
      _element_validate_options_size($field['field_settings']['title_options'], $field['field_settings']['max_length']['title'], t('Title options'));
      _element_validate_options_size($field['field_settings']['generational_options'], $field['field_settings']['max_length']['generational'], t('Generational options'));
      _name_field_settings_validate($field);
      break;
    case 'save':
      return array(
        'field_settings',
      );
    case 'database columns':
      $components = array_filter($field['field_settings']['components']);
      $columns = array();
      foreach (_name_translations($components) as $key => $title) {
        $max = $field['field_settings']['max_length'][$key];
        $columns[$key] = array(
          'type' => 'varchar',
          'length' => is_numeric($max) ? $max : 255,
          'not null' => FALSE,
          'sortable' => TRUE,
          'views' => TRUE,
        );
      }
      return $columns;
    case 'views data':
      $data = content_views_field_views_data($field);
      $db_info = content_database_info($field);
      $table_alias = content_views_tablename($field);

      // Make changes to $data as needed here.
      return $data;
  }
}

/**
 * Drupal does not validate checkboxes by default. With checkbox validate, the
 * user may get two error messages.
 */
function _name_field_minimal_component_requirements($element, &$form_state) {
  $required_field_set = array_flip(array(
    'given',
    'middle',
    'family',
  ));
  $value = array_intersect_key($required_field_set, array_filter((array) $element['#value']));
  if (empty($value)) {
    $required_fields = array_intersect_key(_name_translations(), $required_field_set);
    form_set_error(implode('][', $element['#parents']) . '][given', t('%label must have one of the following components: %components', array(
      '%label' => $element['#title'],
      '%components' => implode(', ', $required_fields),
    )));
  }
}

/**
 * Custom validation of settings values.
 */
function _name_validate_integer_positive($element, &$form_state) {
  $value = $element['#value'];
  if ($value && !is_numeric($value) || $value < 1 || $value > 255) {
    form_set_error(implode('][', $element['#array_parents']), t('%label must be a number between 1 and 255.', array(
      '%label' => $element['#title'],
    )));
  }
}
function _name_field_settings_validate($field) {
  $settings = $field['field_settings'];
  $diff = array_diff_key(array_filter($settings['minimum_components']), array_filter($settings['components']));
  if (count($diff)) {
    $components = array_intersect_key(_name_translations(), $diff);
    form_set_error('field_settings][minimum_components][' . key($diff), t('The following components for %label are not selected for this name field: %components', array(
      '%label' => t('Minimum components'),
      '%components' => implode(', ', $components),
    )));
  }
}

/**
 * Implementation of hook_field().
 */
function name_field($op, &$node, $field, &$items, $teaser, $page) {
  switch ($op) {
    case 'validate':

      // Validation on required is done here.
      // The max. length is done via Drupal.
      // Min. components is done via name_element validation callback.
      if ($field['required']) {
        if (is_array($items)) {
          foreach ($items as $delta => $item) {
            if (!name_content_is_empty($item, $field)) {
              return $items;
            }
          }
        }
        $error_element = empty($items[0]['_error_element']) ? '' : $items[0]['_error_element'] . '][';
        $error_element .= $field['field_name'] . '[0]';

        // TODO
        form_set_error($error_element, t('%label is required.', array(
          '%label' => $field['widget']['label'],
        )));
      }
      return $items;
    case 'sanitize':
      foreach ($items as $delta => $item) {
        foreach (_name_translations() as $key => $title) {
          $items[$delta]['safe'][$key] = empty($item[$key]) ? '' : check_plain($item[$key]);
        }
      }
  }
}

/**
 * Implementation of hook_content_is_empty().
 */
function name_content_is_empty($item, $field) {
  foreach (_name_translations() as $key => $title) {

    // Title has no meaning by itself.
    if ($key == 'title' || $key == 'generational') {
      continue;
    }
    if (!empty($item[$key])) {
      return FALSE;
    }
  }
  return TRUE;
}

/**
 * Implementation of hook_field_formatter_info().
 */
function name_field_formatter_info() {
  $formatters = array();

  // Three formatter types are returned here: default, raw & plain.
  foreach (_name_formatter_output_types() as $type => $title) {
    $type_info = $type == 'default' ? '' : ' (' . $title . ')';
    $formatters[$type] = array(
      'label' => t('Default') . $type_info,
      'field types' => array(
        'name',
      ),
      'multiple values' => CONTENT_HANDLE_CORE,
    );
    foreach (name_get_custom_format_options() as $machine_name => $name_title) {
      $formatters[$type . '_' . $machine_name] = array(
        'label' => $name_title . $type_info,
        'field types' => array(
          'name',
        ),
        'multiple values' => CONTENT_HANDLE_CORE,
      );
    }
  }
  return $formatters;
}

/**
 * Theme function all name field formatters.
 */
function theme_name_formatter_default($element) {

  // Prevents warnings.
  if (!isset($element['#formatter']) || $element['#formatter'] == 'default') {
    $element['#formatter'] = 'default_default';
  }
  list($type, $format) = explode('_', $element['#formatter'], 2);
  $format = name_get_format_by_machine_name($format);
  $parts = array();
  foreach (_name_translations() as $key => $title) {
    if (!empty($element['#item']['safe'][$key])) {
      $parts[$key] = $element['#item']['safe'][$key];
    }
    else {
      $parts[$key] = NULL;
    }
  }
  $output = name_format($parts, $format, array(
    'object' => $element['#node'],
    'type' => 'node',
  ));
  switch ($type) {
    case 'plain':
      return strip_tags($output);
    case 'raw':
      return $output;
    case 'default':
    default:
      return check_plain($output);
  }
}

/**
 * Implementation of hook_widget_info().
 */
function name_widget_info() {
  return array(
    'name_widget' => array(
      'label' => t('Name'),
      'field types' => array(
        'name',
      ),
      'multiple values' => CONTENT_HANDLE_CORE,
      'callbacks' => array(
        'default value' => CONTENT_CALLBACK_DEFAULT,
      ),
    ),
  );
}

/**
 * Implementation of hook_widget_settings().
 */
function name_widget_settings($op, $widget) {
  switch ($op) {
    case 'form':
      $form = array();
      $form['instance_settings'] = array(
        '#tree' => TRUE,
      );
      _name_defaults($widget, 'instance_settings');
      $components = _name_translations();
      $form['instance_settings']['size'] = array();
      $form['instance_settings']['title_display'] = array();
      foreach ($components as $key => $title) {
        $form['instance_settings']['size'][$key] = array(
          '#type' => 'textfield',
          '#title' => t('HTML size property for !title', array(
            '!title' => $title,
          )),
          '#default_value' => $widget['instance_settings']['size'][$key],
          '#required' => FALSE,
          '#size' => 10,
          '#description' => t('The maximum length of the field in characters. This must be between 1 and 255.'),
          '#element_validate' => array(
            '_name_validate_integer_positive',
          ),
        );
        $form['instance_settings']['title_display'][$key] = array(
          '#type' => 'radios',
          '#title' => t('Label display for !title', array(
            '!title' => $title,
          )),
          '#default_value' => $widget['instance_settings']['title_display'][$key],
          '#options' => array(
            'title' => t('above'),
            'description' => t('below'),
            'none' => t('hidden'),
          ),
          '#description' => t('This controls how the label of the name component is displayed in the form.'),
        );
        $form['instance_settings']['inline_css_enabled'][$key] = array(
          '#type' => 'checkbox',
          '#title' => t('Use inline styles for !title', array(
            '!title' => $title,
          )),
          '#default_value' => $widget['instance_settings']['inline_css_enabled'][$key],
        );
      }
      $form['instance_settings']['title_field'] = array(
        '#type' => 'radios',
        '#title' => t('Title field type'),
        '#default_value' => $widget['instance_settings']['title_field'],
        '#required' => TRUE,
        '#options' => array(
          'select' => t('Drop-down'),
        ),
      );
      $form['instance_settings']['generational_field'] = array(
        '#type' => 'radios',
        '#title' => t('Generational field type'),
        '#default_value' => $widget['instance_settings']['generational_field'],
        '#required' => TRUE,
        '#options' => array(
          'select' => t('Drop-down'),
        ),
      );
      $form['instance_settings']['inline_css'] = array(
        '#type' => 'fieldset',
        '#title' => t('Inline CSS styles for name components'),
        '#description' => t('This gives you the option of specifying CSS rules to control the width of the name components. The HTML size for the component is used with the multiplier and the resulting width is rendered as an inline style such as: <em>style="width: 345px;"</em>'),
      );
      $form['instance_settings']['inline_css']['multiplier'] = array(
        '#type' => 'textfield',
        '#title' => t('Multiplier'),
        '#default_value' => $widget['instance_settings']['inline_css']['multiplier'],
        '#required' => TRUE,
        '#description' => t('The width is set by multipling the HTML size with this.'),
      );
      $form['instance_settings']['inline_css']['unit'] = array(
        '#type' => 'radios',
        '#title' => t('CSS Unit'),
        '#default_value' => $widget['instance_settings']['inline_css']['unit'],
        '#required' => TRUE,
        '#options' => array(
          'em' => 'em',
          'px' => 'px',
        ),
      );
      return $form;
    case 'save':
      return array(
        'instance_settings',
      );
  }
}

/**
 * Implementation of hook_widget().
 */
function name_widget(&$form, &$form_state, $field, $items, $delta = 0) {
  $widget = $field['widget'];
  _name_defaults($widget, 'instance_settings');
  _name_defaults($field, 'field_settings');
  $fs = $field['field_settings'];
  $ws = $widget['instance_settings'];
  $element = array(
    '#type' => 'name_element',
    '#title' => $widget['label'],
    '#label' => $widget['label'],
    '#components' => array(),
    '#minimum_components' => array_filter($fs['minimum_components']),
    '#default_value' => isset($items[$delta]) ? $items[$delta] : NULL,
  );

  // Field only property. Do the calculations here.
  $inline_css = $ws['inline_css'];
  $components = array_filter($fs['components']);
  foreach (_name_translations() as $key => $title) {
    if (in_array($key, $components)) {
      $element['#components'][$key]['type'] = 'textfield';
      $size = !empty($ws['size'][$key]) ? $ws['size'][$key] : 60;
      $title_display = isset($ws['title_display'][$key]) ? $ws['title_display'][$key] : 'description';
      $element['#components'][$key]['title'] = check_plain($fs['labels'][$key]);
      $element['#components'][$key]['title_display'] = $title_display;
      $element['#components'][$key]['size'] = $size;
      $element['#components'][$key]['maxlength'] = !empty($fs['max_length'][$key]) ? $fs['max_length'][$key] : 255;
      if (isset($ws[$key . '_field']) && $ws[$key . '_field'] == 'select') {
        $element['#components'][$key]['type'] = 'select';
        $element['#components'][$key]['size'] = 1;
        $options = array_filter(explode("\n", $fs[$key . '_options']));
        foreach ($options as $index => $opt) {
          if (preg_match('/^\\[vocabulary:([0-9]{1,})\\]/', trim($opt), $matches)) {
            unset($options[$index]);
            $max_length = isset($fs['max_length'][$key]) ? $fs['max_length'][$key] : 255;
            foreach (taxonomy_get_tree($matches[1]) as $term) {
              if (drupal_strlen($term->name) <= $max_length) {
                $options[] = $term->name;
              }
            }
          }
        }

        // Options could come from multiple sources, filter duplicates.
        $options = array_unique($options);
        if ($fs && isset($fs['sort_options']) && !empty($fs['sort_options'][$key])) {
          natcasesort($options);
        }
        $default = FALSE;
        foreach ($options as $index => $opt) {
          if (strpos($opt, '--') === 0) {
            unset($options[$index]);
            $default = substr($opt, 2);
          }
        }
        $options = drupal_map_assoc(array_map('trim', $options));
        if ($default !== FALSE) {
          $options = array(
            '' => $default,
          ) + $options;
        }
        $element['#components'][$key]['options'] = $options;
      }
      elseif (isset($ws[$key . '_field']) && $ws[$key . '_field'] == 'autocomplete') {

        // TODO $element['#components'][$key]['autocomplete'] = '';
      }
      if (isset($ws['inline_css_enabled'][$key]) && $ws['inline_css_enabled'][$key]) {
        $width = $size * $inline_css['multiplier'];
        switch ($inline_css['unit']) {
          case 'px':
            $width = round($width) . 'px';
            break;
          case 'em':
          default:
            $width = round($width, 1) . 'em';
            break;
        }
        $element['#components'][$key]['attributes'] = array();
        $element['#components'][$key]['attributes']['style'] = 'width:' . $width . ';';
      }
    }
    else {
      $element['#components'][$key]['exclude'] = TRUE;
    }
  }

  // Used so that hook_field('validate') knows where to
  // flag an error in deeply nested forms.
  if (empty($form['#parents'])) {
    $form['#parents'] = array();
  }
  $element['_error_element'] = array(
    '#type' => 'value',
    '#value' => implode('][', $form['#parents']),
  );
  return $element;
}

/**
 * Helper form element validator.
 */
function _element_validate_options_size($field_options, $max_length, $label) {
  $values = array_filter(explode("\n", $field_options));
  $long_options = array();
  $options = array();
  foreach ($values as $value) {
    $value = trim($value);

    // Blank option - anything goes!
    if (strpos($value, '--') === 0) {
      $options[] = $value;
    }
    elseif (drupal_strlen($value) > $max_length) {
      $long_options[] = $value;
    }
    elseif (!empty($value)) {
      $options[] = $value;
    }
  }
  if (count($long_options)) {
    form_set_error('field_settings][title_options', t('The following options exceed the maximun allowed %label length: %options', array(
      '%options' => implode(', ', $long_options),
      '%label' => $label,
    )));
  }
  elseif (empty($options)) {
    form_set_error('field_settings][title_options', t('%label are required.', array(
      '%label' => $label,
    )));
  }
}

/**
 * Helper function to set the defaults for a name field / widget.
 */
function _name_defaults(&$field, $key) {
  $name_info = name_field_info();
  $field[$key] = isset($field[$key]) ? (array) $field[$key] : array();
  foreach ($name_info['name'][$key] as $index => $defaults) {
    if (!isset($field[$key][$index])) {
      if (is_array($defaults)) {
        if (!array_key_exists($index, $field[$key])) {
          $field[$key][$index] = array();
        }
        $field[$key][$index] += $defaults;
      }
      else {
        $field[$key][$index] = $defaults;
      }
    }
  }
}

/**
 * Implementation of CCK's hook_content_diff_values().
 */
function name_content_diff_values($node, $field, $items) {
  $return = array();
  foreach ($items as $delta => $item) {
    $return[] = implode('~', $item);
  }
  return $return;
}

Functions

Namesort descending Description
name_additional_content_theme
name_construct_components A brigding function to warn users that are using any custom code.
name_content_diff_values Implementation of CCK's hook_content_diff_values().
name_content_is_empty Implementation of hook_content_is_empty().
name_elements Implementation of hook_elements().
name_element_expand The #process callback to create the element.
name_element_pre_render This function forces the error class attribute onto each of the empty element components as required, themes the element and controls the title display.
name_element_validate
name_field Implementation of hook_field().
name_field_formatter_info Implementation of hook_field_formatter_info().
name_field_info Implementation of hook_field_info().
name_field_instance_settings_pre_render
name_field_settings Implementation of hook_field_settings().
name_field_settings_pre_render
name_format This is the main function that formats a name from an array of components.
name_form_content_field_edit_form_alter Provides a hook into the theming of the field and instance settings, using #pre_render callbacks.
name_get_custom_formats Loads a list of all user defined formats.
name_get_custom_format_options Helper function to generate a list of all defined custom formatting options.
name_get_format_by_machine_name
name_menu Implementation of hook_menu().
name_settings Handles the initialization of the Name module settings that are stored in the {variables} table.
name_theme Implement hook_theme().
name_token_list Implementation of hook_token_list().
name_token_values Implementation of hook_token_values().
name_widget Implementation of hook_widget().
name_widget_info Implementation of hook_widget_info().
name_widget_settings Implementation of hook_widget_settings().
theme_name_element Custom theme callback for the name_element.
theme_name_formatter_default Theme function all name field formatters.
_element_validate_options_size Helper form element validator.
_name_defaults Helper function to set the defaults for a name field / widget.
_name_field_minimal_component_requirements Drupal does not validate checkboxes by default. With checkbox validate, the user may get two error messages.
_name_field_settings_validate
_name_formatter_output_types Private helper function to define the formatter types that are available for the CCK and Token modules.
_name_render_component
_name_translations Static cache to reuse translated name components.
_name_validate_integer_positive Custom validation of settings values.