Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
| Total | |
0.00% |
0 / 1 |
|
0.00% |
0 / 6 |
CRAP | |
0.00% |
0 / 216 |
| Datelist | |
0.00% |
0 / 1 |
|
0.00% |
0 / 6 |
5112 | |
0.00% |
0 / 216 |
| getInfo | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 18 |
|||
| valueCallback | |
0.00% |
0 / 1 |
552 | |
0.00% |
0 / 62 |
|||
| processDatelist | |
0.00% |
0 / 1 |
650 | |
0.00% |
0 / 83 |
|||
| validateDatelist | |
0.00% |
0 / 1 |
240 | |
0.00% |
0 / 26 |
|||
| checkEmptyInputs | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 3 |
|||
| incrementRound | |
0.00% |
0 / 1 |
42 | |
0.00% |
0 / 24 |
|||
| <?php | |
| /** | |
| * @file | |
| * Contains \Drupal\Core\Datetime\Element\Datelist. | |
| */ | |
| namespace Drupal\Core\Datetime\Element; | |
| use Drupal\Component\Utility\NestedArray; | |
| use Drupal\Core\Datetime\DateHelper; | |
| use Drupal\Core\Datetime\DrupalDateTime; | |
| use Drupal\Core\Form\FormStateInterface; | |
| /** | |
| * Provides a datelist element. | |
| * | |
| * @FormElement("datelist") | |
| */ | |
| class Datelist extends DateElementBase { | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function getInfo() { | |
| $class = get_class($this); | |
| return array( | |
| '#input' => TRUE, | |
| '#element_validate' => array( | |
| array($class, 'validateDatelist'), | |
| ), | |
| '#process' => array( | |
| array($class, 'processDatelist'), | |
| ), | |
| '#theme' => 'datetime_form', | |
| '#theme_wrappers' => array('datetime_wrapper'), | |
| '#date_part_order' => array('year', 'month', 'day', 'hour', 'minute'), | |
| '#date_year_range' => '1900:2050', | |
| '#date_increment' => 1, | |
| '#date_date_callbacks' => array(), | |
| '#date_timezone' => '', | |
| ); | |
| } | |
| /** | |
| * {@inheritdoc} | |
| * | |
| * Validates the date type to adjust 12 hour time and prevent invalid dates. | |
| * If the date is valid, the date is set in the form. | |
| */ | |
| public static function valueCallback(&$element, $input, FormStateInterface $form_state) { | |
| $parts = $element['#date_part_order']; | |
| $increment = $element['#date_increment']; | |
| $date = NULL; | |
| if ($input !== FALSE) { | |
| $return = $input; | |
| if (empty(static::checkEmptyInputs($input, $parts))) { | |
| if (isset($input['ampm'])) { | |
| if ($input['ampm'] == 'pm' && $input['hour'] < 12) { | |
| $input['hour'] += 12; | |
| } | |
| elseif ($input['ampm'] == 'am' && $input['hour'] == 12) { | |
| $input['hour'] -= 12; | |
| } | |
| unset($input['ampm']); | |
| } | |
| $timezone = !empty($element['#date_timezone']) ? $element['#date_timezone'] : NULL; | |
| $date = DrupalDateTime::createFromArray($input, $timezone); | |
| if ($date instanceof DrupalDateTime && !$date->hasErrors()) { | |
| static::incrementRound($date, $increment); | |
| } | |
| } | |
| } | |
| else { | |
| $return = array_fill_keys($parts, ''); | |
| if (!empty($element['#default_value'])) { | |
| $date = $element['#default_value']; | |
| if ($date instanceof DrupalDateTime && !$date->hasErrors()) { | |
| static::incrementRound($date, $increment); | |
| foreach ($parts as $part) { | |
| switch ($part) { | |
| case 'day': | |
| $format = 'j'; | |
| break; | |
| case 'month': | |
| $format = 'n'; | |
| break; | |
| case 'year': | |
| $format = 'Y'; | |
| break; | |
| case 'hour': | |
| $format = in_array('ampm', $element['#date_part_order']) ? 'g': 'G'; | |
| break; | |
| case 'minute': | |
| $format = 'i'; | |
| break; | |
| case 'second': | |
| $format = 's'; | |
| break; | |
| case 'ampm': | |
| $format = 'a'; | |
| break; | |
| default: | |
| $format = ''; | |
| } | |
| $return[$part] = $date->format($format); | |
| } | |
| } | |
| } | |
| } | |
| $return['object'] = $date; | |
| return $return; | |
| } | |
| /** | |
| * Expands a date element into an array of individual elements. | |
| * | |
| * Required settings: | |
| * - #default_value: A DrupalDateTime object, adjusted to the proper local | |
| * timezone. 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 here. | |
| * This element accepts a date as the default value, and then converts the | |
| * user input strings back into a new date object on submission. No timezone | |
| * adjustment is performed. | |
| * Optional properties include: | |
| * - #date_part_order: Array of date parts indicating the parts and order | |
| * that should be used in the selector, optionally including 'ampm' for | |
| * 12 hour time. Default is array('year', 'month', 'day', 'hour', 'minute'). | |
| * - #date_text_parts: Array of date parts that should be presented as | |
| * text fields instead of drop-down selectors. Default is an empty array. | |
| * - #date_date_callbacks: Array of optional callbacks for the date element. | |
| * - #date_year_range: A description of the range of years to allow, like | |
| * '1900:2050', '-3:+3' or '2000:+3', where the first value describes the | |
| * earliest year and the second the latest year in the range. A year | |
| * in either position means that specific year. A +/- value describes a | |
| * dynamic value that is that many years earlier or later than the current | |
| * year at the time the form is displayed. Defaults to '1900:2050'. | |
| * - #date_increment: The increment to use for minutes and seconds, i.e. | |
| * '15' would show only :00, :15, :30 and :45. Defaults to 1 to show every | |
| * minute. | |
| * - #date_timezone: The local timezone to use when creating dates. Generally | |
| * this should be left empty and it will be set correctly for the user using | |
| * the form. Useful if the default value is empty to designate a desired | |
| * timezone for dates created in form processing. If a default date is | |
| * provided, this value will be ignored, the timezone in the default date | |
| * takes precedence. Defaults to the value returned by | |
| * drupal_get_user_timezone(). | |
| * | |
| * Example usage: | |
| * @code | |
| * $form = array( | |
| * '#type' => 'datelist', | |
| * '#default_value' => new DrupalDateTime('2000-01-01 00:00:00'), | |
| * '#date_part_order' => array('month', 'day', 'year', 'hour', 'minute', 'ampm'), | |
| * '#date_text_parts' => array('year'), | |
| * '#date_year_range' => '2010:2020', | |
| * '#date_increment' => 15, | |
| * ); | |
| * @endcode | |
| * | |
| * @param array $element | |
| * The form element whose value is being processed. | |
| * @param \Drupal\Core\Form\FormStateInterface $form_state | |
| * The current state of the form. | |
| * @param array $complete_form | |
| * The complete form structure. | |
| * | |
| * @return array | |
| */ | |
| public static function processDatelist(&$element, FormStateInterface $form_state, &$complete_form) { | |
| // Load translated date part labels from the appropriate calendar plugin. | |
| $date_helper = new DateHelper(); | |
| // The value callback has populated the #value array. | |
| $date = !empty($element['#value']['object']) ? $element['#value']['object'] : NULL; | |
| // Set a fallback timezone. | |
| if ($date instanceof DrupalDateTime) { | |
| $element['#date_timezone'] = $date->getTimezone()->getName(); | |
| } | |
| elseif (!empty($element['#timezone'])) { | |
| $element['#date_timezone'] = $element['#date_timezone']; | |
| } | |
| else { | |
| $element['#date_timezone'] = drupal_get_user_timezone(); | |
| } | |
| $element['#tree'] = TRUE; | |
| // Determine the order of the date elements. | |
| $order = !empty($element['#date_part_order']) ? $element['#date_part_order'] : array('year', 'month', 'day'); | |
| $text_parts = !empty($element['#date_text_parts']) ? $element['#date_text_parts'] : array(); | |
| // Output multi-selector for date. | |
| foreach ($order as $part) { | |
| switch ($part) { | |
| case 'day': | |
| $options = $date_helper->days($element['#required']); | |
| $format = 'j'; | |
| $title = t('Day'); | |
| break; | |
| case 'month': | |
| $options = $date_helper->monthNamesAbbr($element['#required']); | |
| $format = 'n'; | |
| $title = t('Month'); | |
| break; | |
| case 'year': | |
| $range = static::datetimeRangeYears($element['#date_year_range'], $date); | |
| $options = $date_helper->years($range[0], $range[1], $element['#required']); | |
| $format = 'Y'; | |
| $title = t('Year'); | |
| break; | |
| case 'hour': | |
| $format = in_array('ampm', $element['#date_part_order']) ? 'g': 'G'; | |
| $options = $date_helper->hours($format, $element['#required']); | |
| $title = t('Hour'); | |
| break; | |
| case 'minute': | |
| $format = 'i'; | |
| $options = $date_helper->minutes($format, $element['#required'], $element['#date_increment']); | |
| $title = t('Minute'); | |
| break; | |
| case 'second': | |
| $format = 's'; | |
| $options = $date_helper->seconds($format, $element['#required'], $element['#date_increment']); | |
| $title = t('Second'); | |
| break; | |
| case 'ampm': | |
| $format = 'a'; | |
| $options = $date_helper->ampm($element['#required']); | |
| $title = t('AM/PM'); | |
| break; | |
| default: | |
| $format = ''; | |
| $options = array(); | |
| $title = ''; | |
| } | |
| $default = isset($element['#value'][$part]) && trim($element['#value'][$part]) != '' ? $element['#value'][$part] : ''; | |
| $value = $date instanceof DrupalDateTime && !$date->hasErrors() ? $date->format($format) : $default; | |
| if (!empty($value) && $part != 'ampm') { | |
| $value = intval($value); | |
| } | |
| $element['#attributes']['title'] = $title; | |
| $element[$part] = array( | |
| '#type' => in_array($part, $text_parts) ? 'textfield' : 'select', | |
| '#title' => $title, | |
| '#title_display' => 'invisible', | |
| '#value' => $value, | |
| '#attributes' => $element['#attributes'], | |
| '#options' => $options, | |
| '#required' => $element['#required'], | |
| '#error_no_message' => FALSE, | |
| ); | |
| } | |
| // Allows custom callbacks to alter the element. | |
| if (!empty($element['#date_date_callbacks'])) { | |
| foreach ($element['#date_date_callbacks'] as $callback) { | |
| if (function_exists($callback)) { | |
| $callback($element, $form_state, $date); | |
| } | |
| } | |
| } | |
| return $element; | |
| } | |
| /** | |
| * Validation callback for a datelist element. | |
| * | |
| * If the date is valid, the date object created from the user input is set in | |
| * the form for use by the caller. The work of compiling the user input back | |
| * into a date object is handled by the value callback, so we can use it here. | |
| * We also have the raw input available for validation testing. | |
| * | |
| * @param array $element | |
| * The element being processed. | |
| * @param \Drupal\Core\Form\FormStateInterface $form_state | |
| * The current state of the form. | |
| * @param array $complete_form | |
| * The complete form structure. | |
| */ | |
| public static function validateDatelist(&$element, FormStateInterface $form_state, &$complete_form) { | |
| $input_exists = FALSE; | |
| $input = NestedArray::getValue($form_state->getValues(), $element['#parents'], $input_exists); | |
| if ($input_exists) { | |
| $all_empty = static::checkEmptyInputs($input, $element['#date_part_order']); | |
| // If there's empty input and the field is not required, set it to empty. | |
| if (empty($input['year']) && empty($input['month']) && empty($input['day']) && !$element['#required']) { | |
| $form_state->setValueForElement($element, NULL); | |
| } | |
| // If there's empty input and the field is required, set an error. | |
| elseif (empty($input['year']) && empty($input['month']) && empty($input['day']) && $element['#required']) { | |
| $form_state->setError($element, t('The %field date is required.')); | |
| } | |
| elseif (!empty($all_empty)) { | |
| foreach ($all_empty as $value){ | |
| $form_state->setError($element[$value], t('A value must be selected for %part.', array('%part' => $value))); | |
| } | |
| } | |
| else { | |
| // If the input is valid, set it. | |
| $date = $input['object']; | |
| if ($date instanceof DrupalDateTime && !$date->hasErrors()) { | |
| $form_state->setValueForElement($element, $date); | |
| } | |
| // If the input is invalid, set an error. | |
| else { | |
| $form_state->setError($element, t('The %field date is invalid.', array('%field' => !empty($element['#title']) ? $element['#title'] : ''))); | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * Checks the input array for empty values. | |
| * | |
| * Input array keys are checked against values in the parts array. Elements | |
| * not in the parts array are ignored. Returns an array representing elements | |
| * from the input array that have no value. If no empty values are found, | |
| * returned array is empty. | |
| * | |
| * @param array $input | |
| * Array of individual inputs to check for value. | |
| * @param array $parts | |
| * Array to check input against, ignoring elements not in this array. | |
| * | |
| * @return array | |
| * Array of keys from the input array that have no value, may be empty. | |
| */ | |
| protected static function checkEmptyInputs($input, $parts) { | |
| // Filters out empty array values, any valid value would have a string length. | |
| $filtered_input = array_filter($input, 'strlen'); | |
| return array_diff($parts, array_keys($filtered_input)); | |
| } | |
| /** | |
| * Rounds minutes and seconds to nearest requested value. | |
| * | |
| * @param $date | |
| * | |
| * @param $increment | |
| * | |
| * @return | |
| */ | |
| protected static function incrementRound(&$date, $increment) { | |
| // Round minutes and seconds, if necessary. | |
| if ($date instanceof DrupalDateTime && $increment > 1) { | |
| $day = intval($date->format('j')); | |
| $hour = intval($date->format('H')); | |
| $second = intval(round(intval($date->format('s')) / $increment) * $increment); | |
| $minute = intval($date->format('i')); | |
| if ($second == 60) { | |
| $minute += 1; | |
| $second = 0; | |
| } | |
| $minute = intval(round($minute / $increment) * $increment); | |
| if ($minute == 60) { | |
| $hour += 1; | |
| $minute = 0; | |
| } | |
| $date->setTime($hour, $minute, $second); | |
| if ($hour == 24) { | |
| $day += 1; | |
| $year = $date->format('Y'); | |
| $month = $date->format('n'); | |
| $date->setDate($year, $month, $day); | |
| } | |
| } | |
| return $date; | |
| } | |
| } |