You are here

tzfield.module in Time Zone Field 6

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

Defines a field type for storing timezones.

File

tzfield.module
View source
<?php

/**
 * @file
 * Defines a field type for storing timezones.
 */

/**
 * Implementation of hook_menu().
 */
function tzfield_menu() {
  $items = array();
  $items['tzfield/autocomplete'] = array(
    'title' => t('Timezone field autocomplete'),
    'page callback' => 'tzfield_autocomplete',
    'access arguments' => array(
      'access content',
    ),
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Implementation of hook_theme().
 */
function tzfield_theme() {
  return array(
    'tzfield_select' => array(
      'arguments' => array(
        'element' => NULL,
      ),
    ),
    'tzfield_autocomplete' => array(
      'arguments' => array(
        'element' => NULL,
      ),
    ),
    'tzfield_formatter_default' => array(
      'arguments' => array(
        'element',
      ),
    ),
    'tzfield_formatter_abbreviation' => array(
      'arguments' => array(
        'element',
      ),
      'function' => 'theme_tzfield_formatter_date',
    ),
    'tzfield_formatter_medium' => array(
      'arguments' => array(
        'element',
      ),
      'function' => 'theme_tzfield_formatter_date',
    ),
    'tzfield_formatter_rfc2822' => array(
      'arguments' => array(
        'element',
      ),
      'function' => 'theme_tzfield_formatter_date',
    ),
    'tzfield_none' => array(
      'arguments' => array(
        'widget_type' => NULL,
        'field_name' => NULL,
        'node_type' => NULL,
      ),
    ),
  );
}

/**
 * Implementation of hook_field_info().
 *
 * Here we indicate that the content module will use its default
 * handling for the view of this field.
 */
function tzfield_field_info() {
  return array(
    'timezone' => array(
      'label' => 'Timezone',
      'description' => t('Store a timezone identifier in the database.'),
      'callbacks' => array(
        'tables' => CONTENT_CALLBACK_DEFAULT,
        'arguments' => CONTENT_CALLBACK_DEFAULT,
      ),
    ),
  );
}

/**
 * Implementation of hook_field_settings().
 */
function tzfield_field_settings($op, $field) {
  switch ($op) {
    case 'form':
      $form = array();
      $form['exclude'] = array(
        '#type' => 'select',
        '#multiple' => TRUE,
        '#required' => FALSE,
        '#title' => t('Timezones to be excluded from the option list'),
        '#default_value' => is_array($field['exclude']) ? $field['exclude'] : tzfield_excluded_timezones(),
        '#options' => array(
          '' => '<' . t('none') . '>',
        ) + tzfield_identifiers_list(),
        '#description' => t('Note, many <a href="http://en.wikipedia.org/wiki/List_of_zoneinfo_timezones">timezones</a> exist in PHP only for backward compatibility reasons.'),
      );
      return $form;
    case 'save':
      return array(
        'exclude',
      );
    case 'database columns':
      $columns = array(
        'timezone' => array(
          'type' => 'varchar',
          'length' => 32,
          'not null' => FALSE,
          'sortable' => TRUE,
        ),
      );
      return $columns;
    case 'filters':
      $allowed_values = content_allowed_values($field);
      if (count($allowed_values)) {
        return array(
          'default' => array(
            'list' => $allowed_values,
            'list-type' => 'list',
            'operator' => 'views_handler_operator_or',
            'value-type' => 'array',
          ),
        );
      }
      else {
        return array(
          'like' => array(
            'operator' => 'views_handler_operator_like',
            'handler' => 'views_handler_filter_like',
          ),
        );
      }
  }
}

/**
 * Implementation of hook_field().
 */
function tzfield_field($op, &$node, $field, &$items, $teaser, $page) {
  switch ($op) {
    case 'validate':
      module_load_include('inc', 'content', 'content_node_form');
      $timezones = tzfield_timezones($field);
      foreach ($items as $delta => $item) {
        if (is_array($item) && !empty($item['timezone']) && !in_array($item['timezone'], $timezones)) {
          form_set_error($field['field_name'] . '][' . $delta . '][timezone][timezone', t('%name: This timezone is not valid.', array(
            '%name' => t($field['widget']['label']),
          )));
        }
      }
      return $items;
  }
}

/**
 * Implementation of hook_content_is_empty().
 */
function tzfield_content_is_empty($item, $field) {
  if (empty($item['timezone'])) {
    return TRUE;
  }
  return FALSE;
}

/**
 * Implementation of hook_field_formatter_info().
 */
function tzfield_field_formatter_info() {
  return array(
    'default' => array(
      'label' => 'Timezone name',
      'field types' => array(
        'timezone',
      ),
    ),
    'abbreviation' => array(
      'label' => 'Current timezone abbreviation',
      'field types' => array(
        'timezone',
      ),
    ),
    'medium' => array(
      'label' => 'Current medium date format',
      'field types' => array(
        'timezone',
      ),
    ),
    'rfc2822' => array(
      'label' => 'Current RFC 2822 date',
      'field types' => array(
        'timezone',
      ),
    ),
  );
}

/**
 * Implementation of hook_widget_info().
 */
function tzfield_widget_info() {
  return array(
    'tzfield_select' => array(
      'label' => 'Select list',
      'field types' => array(
        'timezone',
      ),
      'multiple values' => CONTENT_HANDLE_MODULE,
      'callbacks' => array(
        'default value' => CONTENT_CALLBACK_DEFAULT,
      ),
    ),
    'tzfield_autocomplete' => array(
      'label' => 'Autocomplete text field',
      'field types' => array(
        'timezone',
      ),
      'multiple values' => CONTENT_HANDLE_CORE,
      'callbacks' => array(
        'default value' => CONTENT_CALLBACK_DEFAULT,
      ),
    ),
  );
}

/**
 * Implementation of FAPI hook_elements().
 */
function tzfield_elements() {
  return array(
    'tzfield_select' => array(
      '#columns' => array(
        'timezone',
      ),
      '#delta' => 0,
      '#input' => TRUE,
      '#process' => array(
        'tzfield_select_process',
      ),
    ),
    'tzfield_autocomplete' => array(
      '#columns' => array(
        'timezone',
      ),
      '#delta' => 0,
      '#input' => TRUE,
      '#process' => array(
        'tzfield_autocomplete_process',
      ),
      '#autocomplete_path' => FALSE,
    ),
  );
}

/**
 * Implementation of hook_widget().
 */
function tzfield_widget(&$form, &$form_state, $field, $items, $delta = 0) {
  switch ($field['widget']['type']) {
    case 'tzfield_select':
      $element = array(
        '#type' => 'tzfield_select',
        '#default_value' => $items,
      );
      break;
    case 'tzfield_autocomplete':
      $element = array(
        '#type' => 'tzfield_autocomplete',
        '#default_value' => isset($items[$delta]) ? $items[$delta] : NULL,
      );
      break;
  }
  return $element;
}

/**
 * Process an individual select element.
 */
function tzfield_select_process($element, $edit, &$form_state, $form) {
  $field_name = $element['#field_name'];
  $field = $form['#field_info'][$field_name];
  $field_key = $element['#columns'][0];

  // See if this element is in the database format or the transformed format,
  // and transform it if necessary.
  if (is_array($element['#value']) && !array_key_exists($field_key, $element['#value'])) {
    $element['#value'] = tzfield_data2form($element, $element['#default_value'], $field);
  }
  $options = tzfield_options($field);
  $element[$field_key] = array(
    '#type' => 'select',
    '#title' => $element['#title'],
    '#description' => $element['#description'],
    '#required' => isset($element['#required']) ? $element['#required'] : $field['required'],
    '#multiple' => isset($element['#multiple']) ? $element['#multiple'] : $field['multiple'],
    '#options' => $options,
    '#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : NULL,
  );

  // Set #element_validate in a way that it will not wipe out other
  // validation functions already set by other modules.
  if (empty($element['#element_validate'])) {
    $element['#element_validate'] = array();
  }
  array_unshift($element['#element_validate'], 'tzfield_validate');

  // Make sure field info will be available to the validator which
  // does not get the values in $form.
  // TODO for some reason putting the $field array into $form_state['storage']
  // causes the node's hook_form_alter to be invoked twice, garbling the
  // results. Need to investigate why that is happening (a core bug?), but
  // in the meantime avoid using $form_state['storage'] to store anything.
  $form_state['#field_info'][$field['field_name']] = $field;
  return $element;
}

/**
 * Process an individual autocomplete element.
 */
function tzfield_autocomplete_process($element, $edit, &$form_state, $form) {

  // Add a validation step where the value can be unwrapped.
  $field_key = $element['#columns'][0];
  $element[$field_key] = array(
    '#type' => 'text_textfield',
    '#default_value' => isset($element['#value']) ? $element['#value'] : '',
    '#autocomplete_path' => 'tzfield/autocomplete/' . $element['#field_name'],
    '#element_validate' => array(
      'tzfield_autocomplete_validate',
    ),
    // The following values were set by the content module and need
    // to be passed down to the nested element.
    '#field_name' => $element['#field_name'],
    '#type_name' => $element['#type_name'],
    '#delta' => $element['#delta'],
    '#columns' => $element['#columns'],
    '#title' => $element['#title'],
    '#description' => $element['#description'],
  );
  return $element;
}

/**
 * Afterbuild adjustment of the element.
 * Remove the wrapper layer and set the right element's value.
 */
function tzfield_select_validate($element, &$form_state) {
  $field_key = $element['#columns'][0];
  form_set_value($element, $element['#value'][$field_key], $form_state);
}

/**
 * Validate an autocomplete element.
 * Remove the wrapper layer and set the right element's value.
 */
function tzfield_autocomplete_validate($element, &$form_state) {
  $field_key = $element['#columns'][0];
  form_set_value($element, $element['#value'][$field_key], $form_state);
}

/**
 * Implementation of hook_allowed_values().
 */
function tzfield_allowed_values($field) {
  return tzfield_timezones($field);
}

/**
 * Menu callback; Retrieve a pipe delimited string of autocomplete suggestions for timezones
 */
function tzfield_autocomplete($field_name, $string = '') {
  $fields = content_fields();
  $field = $fields[$field_name];
  if (arg(4)) {
    $string .= '/' . arg(4);
  }
  if (arg(5)) {
    $string .= '/' . arg(5);
  }
  $string = trim($string);
  $string = str_replace(' ', '_', $string);
  $string = preg_replace(';[^a-z0-9/_-];i', '', $string);
  $return = $string ? preg_grep(';' . $string . ';i', tzfield_timezones($field)) : array();
  drupal_json($return);
}

/**
 * Fetch an array of all candidate timezones, for use in presenting the selection form to the user.
 */
function tzfield_timezones($field) {
  $timezones = tzfield_identifiers_list();
  if (isset($field['exclude']) && is_array($field['exclude'])) {
    return array_diff($timezones, $field['exclude']);
  }
  else {
    return $timezones;
  }
}

/**
 * Fetch a default array of excluded timezones for use when creating a timezone field.
 * See http://us.php.net/manual/en/timezones.others.php
 */
function tzfield_excluded_timezones() {
  $timezones = tzfield_identifiers_list();
  return preg_grep(';^((Africa|America|Antarctica|Arctic|Asia|Atlantic|Australia|Europe|Indian|Pacific)/.*|UTC)$;', $timezones, PREG_GREP_INVERT);
}

/**
 * Cache the timezone identifiers list as a static variable.
 */
function tzfield_identifiers_list() {
  static $list;
  if (empty($list)) {
    $list = drupal_map_assoc(timezone_identifiers_list());
  }
  return $list;
}

/**
 * Theme function for individual select elements.
 */
function theme_tzfield_select($element) {
  return $element['#children'];
}

/**
 * Theme function for individual autocomplete elements.
 */
function theme_tzfield_autocomplete($element) {
  return $element['#children'];
}

/**
 * Theme function for default timezone field formatter (timezone name).
 */
function theme_tzfield_formatter_default($element) {
  return $element['#item']['timezone'];
}

/**
 * Theme function for date format timezone field formatters.
 */
function theme_tzfield_formatter_date($element) {
  switch ($element['#formatter']) {
    case 'abbreviation':
      $format = 'T';
      break;
    case 'medium':
      $type = 'medium';
      break;
    case 'rfc2822':
      $format = 'r';
      break;
  }
  $format = isset($format) ? $format : variable_get('date_format_' . $type, 'D, m/d/Y - H:i');
  return $element['#item']['timezone'] ? date_format(date_create('now', timezone_open($element['#item']['timezone'])), $format) : NULL;
}

/**
 * Helper function for finding the allowed values list for a field.
 *
 * See if there is a module hook for the option values.
 * Otherwise, try content_allowed_values() for an options list.
 */
function tzfield_options($field) {
  $function = $field['module'] . '_allowed_values';
  $options = function_exists($function) ? $function($field) : (array) content_allowed_values($field);

  // Add an empty choice for non required selects
  if (!$field['required']) {
    if ($field['widget']['type'] == 'tzfield_select') {
      $options = array(
        '' => theme('tzfield_none', $field),
      ) + $options;
    }
  }
  return $options;
}

/**
 * Helper function to transpose the values as stored in the database
 * to the format the widget needs. Can be called anywhere this
 * transformation is needed.
 */
function tzfield_data2form($element, $items, $field) {
  $field_key = $element['#columns'][0];
  $options = tzfield_options($field);
  $items_transposed = content_transpose_array_rows_cols($items);
  $values = isset($items_transposed[$field_key]) && is_array($items_transposed[$field_key]) ? $items_transposed[$field_key] : array();
  $keys = array();
  foreach ($values as $value) {
    $key = array_search($value, array_keys($options));
    if (isset($key)) {
      $keys[] = $value;
    }
  }
  if ($field['multiple']) {
    return array(
      $field_key => $keys,
    );
  }
  else {
    return !empty($keys) ? array(
      $field_key => $value,
    ) : array();
  }
}

/**
 *  Theme the label for the empty value for options that are not required.
 *  The default theme will display N/A for a radio list and blank for a select.
 */
function theme_tzfield_none($field) {
  switch ($field['widget']['type']) {
    case 'tzfield_select':
      return t('- None -');
  }
}

/**
 * FAPI function to validate tzfield element.
 */
function tzfield_validate($element, &$form_state) {

  // Transpose selections from field => delta to delta => field,
  // turning multiple selected options into multiple parent elements.
  // Immediate parent is the delta, need to get back to parent's parent
  // to create multiple elements.
  $field = $form_state['#field_info'][$element['#field_name']];
  $items = tzfield_form2data($element, $field);
  form_set_value($element, $items, $form_state);

  // Check we don't exceed the allowed number of values.
  if ($field['multiple'] >= 2) {

    // Filter out 'none' value (if present, will always be in key 0)
    $field_key = $element['#columns'][0];
    if (isset($items[0][$field_key]) && $items[0][$field_key] === '') {
      unset($items[0]);
    }
    if (count($items) > $field['multiple']) {
      $field_key = $element['#columns'][0];
      form_error($element[$field_key], t('%name: this field cannot hold more that @count values.', array(
        '%name' => t($field['widget']['label']),
        '@count' => $field['multiple'],
      )));
    }
  }
}

/**
 * Helper function to transpose the values returned by submitting the widget
 * to the format to be stored in the field. Can be called anywhere this
 * transformation is needed.
 */
function tzfield_form2data($element, $field) {
  $field_key = $element['#columns'][0];
  $items = (array) $element[$field_key]['#value'];
  $options = tzfield_options($field);
  $values = array_values($items);
  if (empty($values)) {
    $values[] = NULL;
  }
  $result = content_transpose_array_rows_cols(array(
    $field_key => $values,
  ));
  return $result;
}

Functions

Namesort descending Description
theme_tzfield_autocomplete Theme function for individual autocomplete elements.
theme_tzfield_formatter_date Theme function for date format timezone field formatters.
theme_tzfield_formatter_default Theme function for default timezone field formatter (timezone name).
theme_tzfield_none Theme the label for the empty value for options that are not required. The default theme will display N/A for a radio list and blank for a select.
theme_tzfield_select Theme function for individual select elements.
tzfield_allowed_values Implementation of hook_allowed_values().
tzfield_autocomplete Menu callback; Retrieve a pipe delimited string of autocomplete suggestions for timezones
tzfield_autocomplete_process Process an individual autocomplete element.
tzfield_autocomplete_validate Validate an autocomplete element. Remove the wrapper layer and set the right element's value.
tzfield_content_is_empty Implementation of hook_content_is_empty().
tzfield_data2form Helper function to transpose the values as stored in the database to the format the widget needs. Can be called anywhere this transformation is needed.
tzfield_elements Implementation of FAPI hook_elements().
tzfield_excluded_timezones Fetch a default array of excluded timezones for use when creating a timezone field. See http://us.php.net/manual/en/timezones.others.php
tzfield_field Implementation of hook_field().
tzfield_field_formatter_info Implementation of hook_field_formatter_info().
tzfield_field_info Implementation of hook_field_info().
tzfield_field_settings Implementation of hook_field_settings().
tzfield_form2data Helper function to transpose the values returned by submitting the widget to the format to be stored in the field. Can be called anywhere this transformation is needed.
tzfield_identifiers_list Cache the timezone identifiers list as a static variable.
tzfield_menu Implementation of hook_menu().
tzfield_options Helper function for finding the allowed values list for a field.
tzfield_select_process Process an individual select element.
tzfield_select_validate Afterbuild adjustment of the element. Remove the wrapper layer and set the right element's value.
tzfield_theme Implementation of hook_theme().
tzfield_timezones Fetch an array of all candidate timezones, for use in presenting the selection form to the user.
tzfield_validate FAPI function to validate tzfield element.
tzfield_widget Implementation of hook_widget().
tzfield_widget_info Implementation of hook_widget_info().