array('arguments' => array('element' => NULL)), ); } /** * Implementation of hook_elements(). * * Set the #type to date_popup and fill the element #default_value with * a date adjusted to the proper local timezone in datetime format (YYYY-MM-DD HH:MM:SS). * * The element will create two textfields, one for the date and one for the * time. The date textfield will include a jQuery popup calendar date picker, * and the time textfield uses a jQuery timepicker. * * NOTE - Converting a date stored in the database from UTC to the local zone * and converting it back to UTC before storing it is not handled by this * element and must be done in pre-form and post-form processing!! * * #date_timezone * The local timezone to be used to create this date. * * #date_format * The #date_format parts can be in any order, and use any of the normal * separators, but are limited to the following formats: * Y, m, d, H, h, i, s, a * * So valid formats include: * Y-m-d H:i * m/d/Y h:ia * * Invalid formats include things like: * m/j/y G:i * * #date_increment * Increment minutes and seconds by this amount, default is 1. * * #date_year_range * The number of years to go back and forward in a year selector, * default is -3:+3 (3 back and 3 forward). * */ function date_popup_elements() { return array( 'date_popup' => array( '#input' => TRUE, '#tree' => TRUE, '#date_timezone' => date_default_timezone_name(), '#date_format' => variable_get('date_format_short', 'm/d/Y - H:i'), '#date_increment' => 1, '#date_year_range' => '-3:+3', '#process' => array('date_popup_process'), '#element_validate' => array('date_popup_validate'), ), ); } /** * Javascript popup element processing. * Add popup attributes to $element. */ function date_popup_process($element, $edit, $form_state, $form) { date_popup_load(); include_once(drupal_get_path('module', 'date_api') .'/date_api_elements.inc'); // There are some cases, like when using this as a Views form element, // where $edit is empty and $element['#value'] holds an array of input values. // This happens when the processing bypasses the element validation step // that resets the value from the date and time // subparts. $date = NULL; if (!empty($edit) || is_array($element['#value'])) { if (empty($edit)) { $edit = $element['#value']; } $input = $edit['date'] . (!empty($edit['time']) ? ' '. $edit['time'] : ''); $datetime = date_convert_from_custom($input, $element['#date_format']); $date = date_make_date($datetime, $element['#date_timezone'], DATE_DATETIME); } elseif (!empty($element['#value'])) { $date = date_make_date($element['#value'], $element['#date_timezone']); } // TODO keep an eye on this, commented out so it is possible to provide // blank initial value for required date. //elseif ($element['#required']) { // $date = date_now($element['#date_timezone']); //} date_increment_round($date, $element['#date_increment']); $element['#tree'] = TRUE; $element['date'] = date_popup_process_date($element, $edit, $date); $element['time'] = date_popup_process_time($element, $edit, $date); return $element; } /** * Process the date portion of the element. */ function date_popup_process_date(&$element, $edit = NULL, $date = NULL) { $granularity = date_format_order($element['#date_format']); $date_granularity = array_intersect($granularity, array('month', 'day', 'year')); $time_granularity = array_intersect($granularity, array('hour', 'minute', 'second')); $date_format = (date_limit_format($element['#date_format'], $date_granularity)); if (empty($date_granularity)) return array(); // Center the range around the current year, but expand it far // enough so it will pick up the year value in the field in case // the value in the field is outside the initial range. $this_year = date_format(date_now(), 'Y'); $value_year = is_object($date) ? date_format($date, 'Y') : $this_year; if (!empty($value_year) && $this_year != $value_year) { $range = explode(':', $element['#date_year_range']); $min_year = min($value_year, $this_year + $range[0]); $max_year = max($value_year, $this_year + $range[1]); $year_range = sprintf('%+d', $min_year - $this_year) .':'. sprintf('%+d', $max_year - $this_year); } else { $year_range = $element['#date_year_range']; } $settings = "\n". //"prevText:'<". t('Prev') ."', \n". "nextText:'". t('Next') .">', \n". "currentText:'". t('Today') ."', \n". "clearText:'". t('Clear') ."', \n". "closeText:'". t('Close') ."', \n". "firstDay:". variable_get('date_first_day', 0) .", \n". "dayNames: new Array('". implode("','", date_week_days_abbr(TRUE, TRUE, 1)) ."'), \n". "monthNames:new Array('". implode("','", date_month_names(TRUE)) ."'), \n". // Can't get the image to float to the right of the input element, // so turning it off for now. // TODO figure out why this isn't working //"buttonImage: '". base_path() . drupal_get_path('module', 'date_api') ."/images/calendar.png', \n". //"buttonImageOnly: false, \n". "autoPopUp: 'focus', \n". "closeAtTop:false, \n". "speed: 'immediate', \n". "dateFormat:'". date_popup_format_to_popup($date_format) ."', \n". "yearRange:'". $year_range ."'\n". "\n"; // This is just a placeholder to indicate the method to constrain from and to dates. // Not yet implemented. if (isset($fromto)) { $settings .+ ", minDate: (input.id == 'dTo' ? getDate($('#dFrom').val()) : null), \n". "maxDate: (input.id == 'dFrom' ? getDate($('#dTo').val()) : null) "; } drupal_add_js('// Global Killswitch if (Drupal.jsEnabled) { $(document).ready(function() {$(\'.jquery-calendar-'. $element['#id'] .'\').calendar({'. $settings. '}); })}', 'inline'); // Create a unique class for each element so we can use custom settings. $class = date_popup_js_settings_class('jquery-calendar', 'calendar', $settings); $sub_element = array( '#type' => 'textfield', '#default_value' => is_object($date) ? date_format($date, $date_format) : '', '#attributes' => array('class' => $class), '#size' => 20, '#maxlength' => 20, ); // TODO, figure out exactly when we want this description. In many places it is not desired. $element['#description'] .= t(' Format: @date', array('@date' => date($date_format, time()))); return $sub_element; } /** * Process the time portion of the element. */ function date_popup_process_time(&$element, $edit = NULL, $date = NULL) { $granularity = date_format_order($element['#date_format']); $time_granularity = array_intersect($granularity, array('hour', 'minute', 'second')); $time_format = date_popup_format_to_popup_time(date_limit_format($element['#date_format'], $time_granularity)); if (empty($time_granularity)) return array(); $spinner_text = array(t('Now'), t('Previous field'), t('Next field'), t('Increment'), t('Decrement')); $settings = "\n"."show24Hours: ". (strpos($element['#date_format'], 'H') ? 'true' : 'false') .", \n". "showSeconds: ". (in_array('second', $granularity) ? 'true' : 'false') .", \n". "timeSteps: [1,". $element['#date_increment'] .",". (in_array('second', $granularity) ? $element['#date_increment'] : 0) ."], \n". "spinnerImage: ''\n"; // This is just a placeholder to indicate the method to constrain from and to times. // Not yet implemented. if (isset($fromto)) { $settings .+ ", minTime: (input.id == 'tTo' ? getTime($('#tFrom').val()) : null), \n". "maxTime: (input.id == 'tFrom' ? getTime($('#tTo').val()) : null)} "; } drupal_add_js('// Global Killswitch if (Drupal.jsEnabled) { $(document).ready(function() {$(\'.jquery-timeentry-'. $element['#id'] .'\').timeEntry({' .$settings. '}); })}', 'inline'); // Create a unique class for each element so we can use custom settings. $sub_element = array( '#type' => 'textfield', '#default_value' => is_object($date) ? date_format($date, $time_format) : '', '#attributes' => array('class' => 'jquery-timeentry-'. $element['#id']), '#size' => 10, '#maxlength' => 10, ); // TODO, figure out exactly when we want this description. In many places it is not desired. $element['#description'] .= t(' @date', array('@date' => date($time_format, time()))); return ($sub_element); } /** * Massage the input values back into a single date. */ function date_popup_validate($element, &$form_state) { include_once(drupal_get_path('module', 'date_api') .'/date_api_elements.inc'); date_popup_load(); $valid_value = date_popup_input_value($element); if (!empty($valid_value)) { form_set_value($element, $valid_value, $form_state); return; } $error_field = implode('][', $element['#parents']); if (isset($element['#value']['date'])) { $message = NULL; if (!empty($element['#value']['date'])) { $message = t('The %label %date %time is not valid.', array('%label' => $element['#title'], '%date' => $element['#value']['date'], '%time' => $element['#value']['time'])); } elseif ($element['#required']) { $message = t('The %label date cannot be empty.', array('%label' => $element['#title'])); } if (!empty($message)) { form_set_error($error_field .'][date', $message); } } if (isset($element['#value']['time'])) { if (!empty($element['#value']['time']) && !empty($element['#value']['date'])) { // We already displayed a message about this, but need another message // here just to set the error field, so don't repeat the previous // message. $message = t('Please check the %label values.', array('%label' => $element['#title'])); } elseif ($element['#required']) { $message = t('The %label time cannot be empty.', array('%label' => $element['#title'])); } if (!empty($message)) { form_set_error($error_field .'][time', $message); } } form_set_value($element, NULL, $form_state); } /** * Helper function for extracting a date value out of user input. */ function date_popup_input_value($element) { $input = $element['#value']['date']; if (!empty($element['#value']['time'])) { $input .= ' '. $element['#value']['time']; } date_popup_load(); $granularity = date_format_order($element['#date_format']); $value = date_convert_from_custom($input, date_limit_format($element['#date_format'], $granularity)); if (date_is_valid($value, DATE_DATETIME)) { $date = date_make_date($value, $element['#date_timezone'], DATE_DATETIME); $value = date_convert($date, DATE_OBJECT, DATE_DATETIME); return $value; } return NULL; } /** * Allowable date formats. */ function date_popup_date_formats() { return array( 'd/m/Y', 'd-m-Y', 'd.m.Y', 'm/d/Y', 'm-d-Y', 'm.d.Y', 'Y/m/d', 'Y-m-d', 'Y.m.d', ); } /** * Allowable time formats. */ function date_popup_time_formats($with_seconds = FALSE) { return array( 'H:i:s', 'h:i:sA', ); } function date_popup_formats() { $formats = array(); foreach (date_popup_date_formats() as $format) { foreach (date_popup_time_formats() as $time_format) { $formats[] = $format .' '. $time_format; } } return $formats; } /** * Recreate a date format string so it has the values popup expects. * * @param string $format * a normal date format string, like Y-m-d * @return string * a format string in popup format, like YMD- */ function date_popup_format_to_popup($format) { if (empty($format)) { $format = 'Y-m-d'; } $sep = array(); ereg('\/|-|\.| ', $format, $sep); $format = str_replace(array('d', 'j'), 'D', $format); $format = str_replace(array('m', 'n'), 'M', $format); $format = str_replace('y', 'Y', $format); $format = str_replace(array(' ', '/', '-', '.', ':', 'l', 'z', 'w', 'W', 'g', 'G', 'h', 'H', 'i', 's', 'a', 'A'), '', $format); return $format . $sep[0]; } /** * Recreate a date format string so it has the values popup expects. * * @param string $format * a normal date format string, like Y-m-d * @return string * a format string in popup format, like YMD- */ function date_popup_format_to_popup_time($format) { if (empty($format)) { $format = 'H:i'; } $format = str_replace(array('G'), 'H', $format); $format = str_replace(array('g'), 'h', $format); $format = str_replace(array('a'), 'A', $format); $format = str_replace(array(' ', '/', '-', '.', 'l', 'z', 'w', 'W', 'd', 'j', 'm', 'n', 'y', 'Y'), '', $format); return $format; } /** * Reconstruct popup format string into normal format string. * * @param string $format * a string in popup format, like YMD- * @return string * a normal date format string, like Y-m-d */ function date_popup_popup_to_format($format) { $sep = substr($format, -1); ereg('(MDY)', $format, $parts); return implode($sep, $parts); } /** * Format a date popup element. * * Use a class that will float date and time next to each other. */ function theme_date_popup($element) { return '
'. theme('form_element', $element, $element['#children']) .'
'; }