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' => array()), '#validate' => array('date_popup_validate' => array()), ), ); } /** * Javascript popup element processing. * Add popup attributes to $element. * * In regular FAPI processing $element['#value'] will contain a string * value before the form is submitted, and an array during submission. * * In regular FAPI processing $edit is empty until the form is submitted * when it will contain an array. * * When used as a Views widget, $edit is always empty, and the validation * step is bypassed. $element['#value'] is the only available value, * but it is a string before submission and an array afterward. * */ function date_popup_process($element, $edit = NULL) { date_popup_load(); $granularity = date_format_order($element['#date_format']); require_once('./'. drupal_get_path('module', 'date_api') .'/date_api_elements.inc'); if (empty($edit)) { $edit = date_views_filter_value($element); } $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, $granularity); } elseif (!empty($element['#value'])) { $date = date_make_date($element['#value'], $element['#date_timezone'], DATE_DATETIME, $granularity); } date_increment_round($date, $element['#date_increment']); $granularity = date_format_order($element['#date_format']); $element['#tree'] = TRUE; $element['#granularity'] = $granularity; $element['date'] = date_popup_process_date($element, $edit, $date); $element['time'] = date_popup_process_time($element, $edit, $date); // Add #date_float to allow date parts to float together on the same line. $class = 'container-inline-date'; if (empty($element['#date_float'])) { $class .= ' date-clear-block'; } if (isset($element['#attributes']) || isset($element['#attributes']['class'])) { $element['#attributes']['class'] .= ' '. $class; } else { $element['#attributes'] = array('class' => $class); } if (isset($element['#validate'])) { array_push($element['#validate'], array('date_popup_validate' => array())); } else { $element['#validate'] = array('date_popup_validate' => array()); } return $element; } /** * Process the date portion of the element. */ function date_popup_process_date(&$element, $edit = NULL, $date = NULL) { $granularity = $element['#granularity']; $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. // The datepicker can't handle zero or negative values like 0:+1 // even though the Date API can handle them, so rework the value // we pass to the datepicker to use defaults it can accept. $this_year = date_format(date_now(), 'Y'); $value_year = is_object($date) ? date_format($date, 'Y') : $this_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); $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 ($fromto) { $settings .+ ", minDate: (input.id == 'dTo' ? getDate($('#dFrom').val()) : null), \n". "maxDate: (input.id == 'dFrom' ? getDate($('#dTo').val()) : null) "; } // 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' => (!empty($element['#value']['date']) || !empty($edit['date'])) && is_object($date) ? date_format($date, $date_format) : '', '#attributes' => array('class' => 'date_popup '. $class), '#size' => !empty($element['#size']) ? $element['#size'] : 20, '#maxlength' => !empty($element['#maxlength']) ? $element['#maxlength'] : 30, ); // 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 = $element['#granularity']; $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') !== FALSE ? '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 ($fromto) { $settings .+ ", minTime: (input.id == 'tTo' ? getTime($('#tFrom').val()) : null), \n". "maxTime: (input.id == 'tFrom' ? getTime($('#tTo').val()) : null)} "; } // Create a unique class for each set of custom settings. $class = date_popup_js_settings_class('jquery-timeentry', 'timeEntry', $settings); $sub_element = array( '#type' => 'textfield', '#default_value' => (!empty($element['#value']['time']) || !empty($edit['time'])) && is_object($date) ? date_format($date, $time_format) : '', '#attributes' => array('class' => $class), '#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) { $granularity = $element['#granularity']; $date_granularity = array_intersect($granularity, array('month', 'day', 'year')); $time_granularity = array_intersect($granularity, array('hour', 'minute', 'second')); // If the field is empty and not required, set it to empty and return. // If the field is empty and required, set error message and return. $error_field = implode('][', $element['#parents']); if (empty($element['#value']['date'])) { if ($element['#required']) { // Set message on both date and time to get them highlighted properly. $message = t('%field is required.', array('%field' => $element['#title'])); if (!empty($date_granularity)) { form_set_error($error_field .'][date', $message); $message = ' '; } if (!empty($time_granularity)) { form_set_error($error_field .'][time', $message); } } form_set_value($element, NULL); return; } require_once('./'. drupal_get_path('module', 'date_api') .'/date_api_elements.inc'); date_popup_load(); $value = date_popup_input_value($element); // If the created date is valid, set it. if (!empty($value)) { form_set_value($element, $value, $form_state); return; } else { // Set message on both date and time to get them highlighted properly. $message = t('%field is invalid.', array('%field' => $element['#title'])); if (!empty($date_granularity)) { form_set_error($error_field .'][date', $message); $message = ' '; } if (!empty($time_granularity)) { form_set_error($error_field .'][time', $message); } } form_set_value($element, NULL); } /** * Helper function for extracting a date value out of user input. * * @param autocomplete * Should we add a time value to complete the date if there is no time? * Useful anytime the time value is optional. */ function date_popup_input_value($element, $auto_complete = FALSE) { date_popup_load(); $granularity = date_format_order($element['#date_format']); $format = $element['#date_format']; $format = strtr($format, timepicker_format_replacements()); $format = date_limit_format($format, $granularity); // Evaluate date and time parts separately since we can't know or care // how they're combined in the complete date format. $time_format = date_limit_format($format, array('hour', 'minute', 'second')); $date_format = date_limit_format($format, array('year', 'month', 'day')); $value = ''; if (is_array($element['#value']) && !empty($element['#value']['date'])) { $date = date_convert_from_custom(trim(!empty($element['#value']['date']) ? $element['#value']['date'] : ''), $date_format); $time = date_convert_from_custom(trim(!empty($element['#value']['time']) ? $element['#value']['time'] : ''), $time_format); $value = trim(substr($date, 0, 10) .' '. substr($time, 11, 8)); } if (date_is_valid($value, DATE_DATETIME, $granularity)) { $date = date_make_date($value, $element['#date_timezone'], DATE_DATETIME, $granularity); $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 = strtr($format, timepicker_format_replacements()); $format = str_replace(array(' ', '/', '-', '.', ',', 'F', 'M', 'l', 'z', 'w', 'W', 'd', 'j', 'm', 'n', 'y', 'Y'), '', $format); return $format; } function timepicker_format_replacements() { return array( 'G' => 'H', 'g' => 'h', 'a' => 'A', ); } /** * 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) { $output = ''; $class = 'container-inline-date'; // Add #date_float to allow date parts to float together on the same line. if (empty($element['#date_float'])) { $class .= ' date-clear-block'; } $element['#attributes']['class'] .= $class; if (isset($element['#children'])) { $output = $element['#children']; } return '