tzfield.module in Time Zone Field 6
Same filename and directory in other branches
Defines a field type for storing timezones.
File
tzfield.moduleView 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
Name | 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(). |