Issue #2328061 by tim.plunkett: Move datetime's FormElement #type classes in Core.
parent
3467f05416
commit
12ee21a360
|
@ -382,6 +382,26 @@ core.base_field_override.*.*.*:
|
||||||
type: field_config_base
|
type: field_config_base
|
||||||
label: 'Base field bundle override'
|
label: 'Base field bundle override'
|
||||||
|
|
||||||
|
core.date_format.*:
|
||||||
|
type: config_entity
|
||||||
|
label: 'Date format'
|
||||||
|
mapping:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
label: 'ID'
|
||||||
|
label:
|
||||||
|
type: label
|
||||||
|
label: 'Label'
|
||||||
|
locked:
|
||||||
|
type: boolean
|
||||||
|
label: 'Locked'
|
||||||
|
pattern:
|
||||||
|
type: date_format
|
||||||
|
label: 'PHP date format'
|
||||||
|
langcode:
|
||||||
|
type: string
|
||||||
|
label: 'Default language'
|
||||||
|
|
||||||
# Schema for the String field type.
|
# Schema for the String field type.
|
||||||
|
|
||||||
field.string.instance_settings:
|
field.string.instance_settings:
|
||||||
|
|
|
@ -843,6 +843,68 @@ function template_preprocess_time(&$variables) {
|
||||||
$variables['attributes'] = new Attribute($variables['attributes']);
|
$variables['attributes'] = new Attribute($variables['attributes']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares variables for datetime form element templates.
|
||||||
|
*
|
||||||
|
* The datetime form element serves as a wrapper around the date element type,
|
||||||
|
* which creates a date and a time component for a date.
|
||||||
|
*
|
||||||
|
* Default template: datetime-form.html.twig.
|
||||||
|
*
|
||||||
|
* @param array $variables
|
||||||
|
* An associative array containing:
|
||||||
|
* - element: An associative array containing the properties of the element.
|
||||||
|
* Properties used: #title, #value, #options, #description, #required,
|
||||||
|
* #attributes.
|
||||||
|
*
|
||||||
|
* @see form_process_datetime()
|
||||||
|
*/
|
||||||
|
function template_preprocess_datetime_form(&$variables) {
|
||||||
|
$element = $variables['element'];
|
||||||
|
|
||||||
|
$variables['attributes'] = array();
|
||||||
|
if (isset($element['#id'])) {
|
||||||
|
$variables['attributes']['id'] = $element['#id'];
|
||||||
|
}
|
||||||
|
if (!empty($element['#attributes']['class'])) {
|
||||||
|
$variables['attributes']['class'] = (array) $element['#attributes']['class'];
|
||||||
|
}
|
||||||
|
$variables['attributes']['class'][] = 'container-inline';
|
||||||
|
|
||||||
|
$variables['content'] = $element;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares variables for datetime form wrapper templates.
|
||||||
|
*
|
||||||
|
* Default template: datetime-wrapper.html.twig.
|
||||||
|
*
|
||||||
|
* @param array $variables
|
||||||
|
* An associative array containing:
|
||||||
|
* - element: An associative array containing the properties of the element.
|
||||||
|
* Properties used: #title, #children, #required, #attributes.
|
||||||
|
*/
|
||||||
|
function template_preprocess_datetime_wrapper(&$variables) {
|
||||||
|
$element = $variables['element'];
|
||||||
|
|
||||||
|
if (!empty($element['#title'])) {
|
||||||
|
$variables['title'] = $element['#title'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($element['#description'])) {
|
||||||
|
$variables['description'] = $element['#description'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$title_attributes = array('class' => array('label'));
|
||||||
|
// For required datetime fields a 'form-required' class is appended to the
|
||||||
|
// label attributes.
|
||||||
|
if (!empty($element['#required'])) {
|
||||||
|
$title_attributes['class'][] = 'form-required';
|
||||||
|
}
|
||||||
|
$variables['title_attributes'] = new Attribute($title_attributes);
|
||||||
|
$variables['content'] = $element['#children'];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepares variables for status message templates.
|
* Prepares variables for status message templates.
|
||||||
*
|
*
|
||||||
|
@ -2183,6 +2245,14 @@ function drupal_common_theme() {
|
||||||
'variables' => array('timestamp' => NULL, 'text' => NULL, 'attributes' => array(), 'html' => FALSE),
|
'variables' => array('timestamp' => NULL, 'text' => NULL, 'attributes' => array(), 'html' => FALSE),
|
||||||
'template' => 'time',
|
'template' => 'time',
|
||||||
),
|
),
|
||||||
|
'datetime_form' => array(
|
||||||
|
'template' => 'datetime-form',
|
||||||
|
'render element' => 'element',
|
||||||
|
),
|
||||||
|
'datetime_wrapper' => array(
|
||||||
|
'template' => 'datetime-wrapper',
|
||||||
|
'render element' => 'element',
|
||||||
|
),
|
||||||
'status_messages' => array(
|
'status_messages' => array(
|
||||||
'variables' => array('display' => NULL),
|
'variables' => array('display' => NULL),
|
||||||
'template' => 'status-messages',
|
'template' => 'status-messages',
|
||||||
|
|
|
@ -2,10 +2,10 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @file
|
* @file
|
||||||
* Contains \Drupal\system\DateFormatInterface.
|
* Contains \Drupal\Core\Datetime\DateFormatInterface.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Drupal\system;
|
namespace Drupal\Core\Datetime;
|
||||||
|
|
||||||
use Drupal\Core\Config\Entity\ConfigEntityInterface;
|
use Drupal\Core\Config\Entity\ConfigEntityInterface;
|
||||||
use Drupal\Core\Datetime\DrupalDateTime;
|
use Drupal\Core\Datetime\DrupalDateTime;
|
|
@ -1,7 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* @file
|
* @file
|
||||||
* Contains \Drupal\datetime\DateHelper.
|
* Contains \Drupal\Core\Datetime\DateHelper.
|
||||||
*
|
*
|
||||||
* Lots of helpful functions for use in massaging dates, specific to the the
|
* Lots of helpful functions for use in massaging dates, specific to the the
|
||||||
* Gregorian calendar system. The values include both translated and
|
* Gregorian calendar system. The values include both translated and
|
||||||
|
@ -14,7 +14,7 @@
|
||||||
* translation should be hard-coded and wrapped in t() so the translation system
|
* translation should be hard-coded and wrapped in t() so the translation system
|
||||||
* will be able to process them.
|
* will be able to process them.
|
||||||
*/
|
*/
|
||||||
namespace Drupal\datetime;
|
namespace Drupal\Core\Datetime;
|
||||||
|
|
||||||
use Drupal\Core\Datetime\DrupalDateTime;
|
use Drupal\Core\Datetime\DrupalDateTime;
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* Contains \Drupal\Core\Datetime\Element\DateElementBase.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Drupal\Core\Datetime\Element;
|
||||||
|
|
||||||
|
use Drupal\Core\Datetime\DrupalDateTime;
|
||||||
|
use Drupal\Core\Render\Element\FormElement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a base class for date elements.
|
||||||
|
*/
|
||||||
|
abstract class DateElementBase extends FormElement {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies the start and end year to use as a date range.
|
||||||
|
*
|
||||||
|
* Handles a string like -3:+3 or 2001:2010 to describe a dynamic range of
|
||||||
|
* minimum and maximum years to use in a date selector.
|
||||||
|
*
|
||||||
|
* Centers the range around the current year, if any, but expands 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.
|
||||||
|
*
|
||||||
|
* @param string $string
|
||||||
|
* A min and max year string like '-3:+1' or '2000:2010' or '2000:+3'.
|
||||||
|
* @param object $date
|
||||||
|
* (optional) A date object to test as a default value. Defaults to NULL.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* A numerically indexed array, containing the minimum and maximum year
|
||||||
|
* described by this pattern.
|
||||||
|
*/
|
||||||
|
protected static function datetimeRangeYears($string, $date = NULL) {
|
||||||
|
$this_year = date_format(new DrupalDateTime(), 'Y');
|
||||||
|
list($min_year, $max_year) = explode(':', $string);
|
||||||
|
|
||||||
|
// Valid patterns would be -5:+5, 0:+1, 2008:2010.
|
||||||
|
$plus_pattern = '@[\+|\-][0-9]{1,4}@';
|
||||||
|
$year_pattern = '@^[0-9]{4}@';
|
||||||
|
if (!preg_match($year_pattern, $min_year, $matches)) {
|
||||||
|
if (preg_match($plus_pattern, $min_year, $matches)) {
|
||||||
|
$min_year = $this_year + $matches[0];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$min_year = $this_year;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!preg_match($year_pattern, $max_year, $matches)) {
|
||||||
|
if (preg_match($plus_pattern, $max_year, $matches)) {
|
||||||
|
$max_year = $this_year + $matches[0];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$max_year = $this_year;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We expect the $min year to be less than the $max year. Some custom values
|
||||||
|
// for -99:+99 might not obey that.
|
||||||
|
if ($min_year > $max_year) {
|
||||||
|
$temp = $max_year;
|
||||||
|
$max_year = $min_year;
|
||||||
|
$min_year = $temp;
|
||||||
|
}
|
||||||
|
// If there is a current value, stretch the range to include it.
|
||||||
|
$value_year = $date instanceOf DrupalDateTime ? $date->format('Y') : '';
|
||||||
|
if (!empty($value_year)) {
|
||||||
|
$min_year = min($value_year, $min_year);
|
||||||
|
$max_year = max($value_year, $max_year);
|
||||||
|
}
|
||||||
|
return array($min_year, $max_year);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,361 @@
|
||||||
|
<?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 (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 = !empty($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'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
|
||||||
|
// 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.'));
|
||||||
|
}
|
||||||
|
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.'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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($date, 'j'));
|
||||||
|
$hour = intval(date_format($date, 'H'));
|
||||||
|
$second = intval(round(intval(date_format($date, 's')) / $increment) * $increment);
|
||||||
|
$minute = intval(date_format($date, 'i'));
|
||||||
|
if ($second == 60) {
|
||||||
|
$minute += 1;
|
||||||
|
$second = 0;
|
||||||
|
}
|
||||||
|
$minute = intval(round($minute / $increment) * $increment);
|
||||||
|
if ($minute == 60) {
|
||||||
|
$hour += 1;
|
||||||
|
$minute = 0;
|
||||||
|
}
|
||||||
|
date_time_set($date, $hour, $minute, $second);
|
||||||
|
if ($hour == 24) {
|
||||||
|
$day += 1;
|
||||||
|
$year = date_format($date, 'Y');
|
||||||
|
$month = date_format($date, 'n');
|
||||||
|
date_date_set($date, $year, $month, $day);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $date;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,431 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* Contains \Drupal\Core\Datetime\Element\Datetime.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Drupal\Core\Datetime\Element;
|
||||||
|
|
||||||
|
use Drupal\Component\Utility\NestedArray;
|
||||||
|
use Drupal\Core\Datetime\DrupalDateTime;
|
||||||
|
use Drupal\Core\Form\FormStateInterface;
|
||||||
|
use Drupal\Core\Datetime\Entity\DateFormat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a datetime element.
|
||||||
|
*
|
||||||
|
* @FormElement("datetime")
|
||||||
|
*/
|
||||||
|
class Datetime extends DateElementBase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \DateTimeInterface
|
||||||
|
*/
|
||||||
|
protected static $dateExample;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getInfo() {
|
||||||
|
$date_format = '';
|
||||||
|
$time_format = '';
|
||||||
|
// Date formats cannot be loaded during install or update.
|
||||||
|
if (!defined('MAINTENANCE_MODE')) {
|
||||||
|
if ($date_format_entity = DateFormat::load('html_date')) {
|
||||||
|
/** @var $date_format_entity \Drupal\Core\Datetime\DateFormatInterface */
|
||||||
|
$date_format = $date_format_entity->getPattern();
|
||||||
|
}
|
||||||
|
if ($time_format_entity = DateFormat::load('html_time')) {
|
||||||
|
/** @var $time_format_entity \Drupal\Core\Datetime\DateFormatInterface */
|
||||||
|
$time_format = $time_format_entity->getPattern();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$class = get_class($this);
|
||||||
|
return array(
|
||||||
|
'#input' => TRUE,
|
||||||
|
'#element_validate' => array(
|
||||||
|
array($class, 'validateDatetime'),
|
||||||
|
),
|
||||||
|
'#process' => array(
|
||||||
|
array($class, 'processDatetime'),
|
||||||
|
array($class, 'processGroup'),
|
||||||
|
),
|
||||||
|
'#pre_render' => array(
|
||||||
|
array($class, 'preRenderGroup'),
|
||||||
|
),
|
||||||
|
'#theme' => 'datetime_form',
|
||||||
|
'#theme_wrappers' => array('datetime_wrapper'),
|
||||||
|
'#date_date_format' => $date_format,
|
||||||
|
'#date_date_element' => 'date',
|
||||||
|
'#date_date_callbacks' => array(),
|
||||||
|
'#date_time_format' => $time_format,
|
||||||
|
'#date_time_element' => 'time',
|
||||||
|
'#date_time_callbacks' => array(),
|
||||||
|
'#date_year_range' => '1900:2050',
|
||||||
|
'#date_increment' => 1,
|
||||||
|
'#date_timezone' => '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
|
||||||
|
if ($input !== FALSE) {
|
||||||
|
$date_input = $element['#date_date_element'] != 'none' && !empty($input['date']) ? $input['date'] : '';
|
||||||
|
$time_input = $element['#date_time_element'] != 'none' && !empty($input['time']) ? $input['time'] : '';
|
||||||
|
$date_format = $element['#date_date_element'] != 'none' ? static::getHtml5DateFormat($element) : '';
|
||||||
|
$time_format = $element['#date_time_element'] != 'none' ? static::getHtml5TimeFormat($element) : '';
|
||||||
|
$timezone = !empty($element['#date_timezone']) ? $element['#date_timezone'] : NULL;
|
||||||
|
|
||||||
|
// Seconds will be omitted in a post in case there's no entry.
|
||||||
|
if (!empty($time_input) && strlen($time_input) == 5) {
|
||||||
|
$time_input .= ':00';
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$date_time_format = trim($date_format . ' ' . $time_format);
|
||||||
|
$date_time_input = trim($date_input . ' ' . $time_input);
|
||||||
|
$date = DrupalDateTime::createFromFormat($date_time_format, $date_time_input, $timezone);
|
||||||
|
}
|
||||||
|
catch (\Exception $e) {
|
||||||
|
$date = NULL;
|
||||||
|
}
|
||||||
|
$input = array(
|
||||||
|
'date' => $date_input,
|
||||||
|
'time' => $time_input,
|
||||||
|
'object' => $date,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$date = $element['#default_value'];
|
||||||
|
if ($date instanceOf DrupalDateTime && !$date->hasErrors()) {
|
||||||
|
$input = array(
|
||||||
|
'date' => $date->format($element['#date_date_format']),
|
||||||
|
'time' => $date->format($element['#date_time_format']),
|
||||||
|
'object' => $date,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$input = array(
|
||||||
|
'date' => '',
|
||||||
|
'time' => '',
|
||||||
|
'object' => NULL,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $input;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expands a datetime element type into date and/or time elements.
|
||||||
|
*
|
||||||
|
* All form elements are designed to have sane defaults so any or all can be
|
||||||
|
* omitted. Both the date and time components are configurable so they can be
|
||||||
|
* output as HTML5 datetime elements or not, as desired.
|
||||||
|
*
|
||||||
|
* Examples of possible configurations include:
|
||||||
|
* HTML5 date and time:
|
||||||
|
* #date_date_element = 'date';
|
||||||
|
* #date_time_element = 'time';
|
||||||
|
* HTML5 datetime:
|
||||||
|
* #date_date_element = 'datetime';
|
||||||
|
* #date_time_element = 'none';
|
||||||
|
* HTML5 time only:
|
||||||
|
* #date_date_element = 'none';
|
||||||
|
* #date_time_element = 'time'
|
||||||
|
* Non-HTML5:
|
||||||
|
* #date_date_element = 'text';
|
||||||
|
* #date_time_element = 'text';
|
||||||
|
*
|
||||||
|
* 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_date_format: A date format string that describes the format that
|
||||||
|
* should be displayed to the end user for the date. When using HTML5
|
||||||
|
* elements the format MUST use the appropriate HTML5 format for that
|
||||||
|
* element, no other format will work. See the format_date() function for a
|
||||||
|
* list of the possible formats and HTML5 standards for the HTML5
|
||||||
|
* requirements. Defaults to the right HTML5 format for the chosen element
|
||||||
|
* if a HTML5 element is used, otherwise defaults to
|
||||||
|
* entity_load('date_format', 'html_date')->getPattern().
|
||||||
|
* - #date_date_element: The date element. Options are:
|
||||||
|
* - datetime: Use the HTML5 datetime element type.
|
||||||
|
* - datetime-local: Use the HTML5 datetime-local element type.
|
||||||
|
* - date: Use the HTML5 date element type.
|
||||||
|
* - text: No HTML5 element, use a normal text field.
|
||||||
|
* - none: Do not display a date element.
|
||||||
|
* - #date_date_callbacks: Array of optional callbacks for the date element.
|
||||||
|
* Can be used to add a jQuery datepicker.
|
||||||
|
* - #date_time_element: The time element. Options are:
|
||||||
|
* - time: Use a HTML5 time element type.
|
||||||
|
* - text: No HTML5 element, use a normal text field.
|
||||||
|
* - none: Do not display a time element.
|
||||||
|
* - #date_time_format: A date format string that describes the format that
|
||||||
|
* should be displayed to the end user for the time. When using HTML5
|
||||||
|
* elements the format MUST use the appropriate HTML5 format for that
|
||||||
|
* element, no other format will work. See the format_date() function for
|
||||||
|
* a list of the possible formats and HTML5 standards for the HTML5
|
||||||
|
* requirements. Defaults to the right HTML5 format for the chosen element
|
||||||
|
* if a HTML5 element is used, otherwise defaults to
|
||||||
|
* entity_load('date_format', 'html_time')->getPattern().
|
||||||
|
* - #date_time_callbacks: An array of optional callbacks for the time
|
||||||
|
* element. Can be used to add a jQuery timepicker or an 'All day' checkbox.
|
||||||
|
* - #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. Used in jQueryUI datepicker year
|
||||||
|
* range and HTML5 min/max date settings. 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. Used for HTML5 step values and
|
||||||
|
* jQueryUI datepicker settings. 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' => 'datetime',
|
||||||
|
* '#default_value' => new DrupalDateTime('2000-01-01 00:00:00'),
|
||||||
|
* '#date_date_element' => 'date',
|
||||||
|
* '#date_time_element' => 'none',
|
||||||
|
* '#date_year_range' => '2010:+3',
|
||||||
|
* );
|
||||||
|
* @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
|
||||||
|
* The form element whose value has been processed.
|
||||||
|
*/
|
||||||
|
public static function processDatetime(&$element, FormStateInterface $form_state, &$complete_form) {
|
||||||
|
$format_settings = array();
|
||||||
|
// 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'] = drupal_get_user_timezone();
|
||||||
|
}
|
||||||
|
|
||||||
|
$element['#tree'] = TRUE;
|
||||||
|
|
||||||
|
if ($element['#date_date_element'] != 'none') {
|
||||||
|
|
||||||
|
$date_format = $element['#date_date_element'] != 'none' ? static::getHtml5DateFormat($element) : '';
|
||||||
|
$date_value = !empty($date) ? $date->format($date_format, $format_settings) : $element['#value']['date'];
|
||||||
|
|
||||||
|
// Creating format examples on every individual date item is messy, and
|
||||||
|
// placeholders are invalid for HTML5 date and datetime, so an example
|
||||||
|
// format is appended to the title to appear in tooltips.
|
||||||
|
$extra_attributes = array(
|
||||||
|
'title' => t('Date (i.e. !format)', array('!format' => static::formatExample($date_format))),
|
||||||
|
'type' => $element['#date_date_element'],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Adds the HTML5 date attributes.
|
||||||
|
if ($date instanceOf DrupalDateTime && !$date->hasErrors()) {
|
||||||
|
$html5_min = clone($date);
|
||||||
|
$range = static::datetimeRangeYears($element['#date_year_range'], $date);
|
||||||
|
$html5_min->setDate($range[0], 1, 1)->setTime(0, 0, 0);
|
||||||
|
$html5_max = clone($date);
|
||||||
|
$html5_max->setDate($range[1], 12, 31)->setTime(23, 59, 59);
|
||||||
|
|
||||||
|
$extra_attributes += array(
|
||||||
|
'min' => $html5_min->format($date_format, $format_settings),
|
||||||
|
'max' => $html5_max->format($date_format, $format_settings),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$element['date'] = array(
|
||||||
|
'#type' => 'date',
|
||||||
|
'#title' => t('Date'),
|
||||||
|
'#title_display' => 'invisible',
|
||||||
|
'#value' => $date_value,
|
||||||
|
'#attributes' => $element['#attributes'] + $extra_attributes,
|
||||||
|
'#required' => $element['#required'],
|
||||||
|
'#size' => max(12, strlen($element['#value']['date'])),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($element['#date_time_element'] != 'none') {
|
||||||
|
|
||||||
|
$time_format = $element['#date_time_element'] != 'none' ? static::getHtml5TimeFormat($element) : '';
|
||||||
|
$time_value = !empty($date) ? $date->format($time_format, $format_settings) : $element['#value']['time'];
|
||||||
|
|
||||||
|
// Adds the HTML5 attributes.
|
||||||
|
$extra_attributes = array(
|
||||||
|
'title' => t('Time (i.e. !format)', array('!format' => static::formatExample($time_format))),
|
||||||
|
'type' => $element['#date_time_element'],
|
||||||
|
'step' => $element['#date_increment'],
|
||||||
|
);
|
||||||
|
$element['time'] = array(
|
||||||
|
'#type' => 'date',
|
||||||
|
'#title' => t('Time'),
|
||||||
|
'#title_display' => 'invisible',
|
||||||
|
'#value' => $time_value,
|
||||||
|
'#attributes' => $element['#attributes'] + $extra_attributes,
|
||||||
|
'#required' => $element['#required'],
|
||||||
|
'#size' => 12,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Allows custom callbacks to alter the element.
|
||||||
|
if (!empty($element['#date_time_callbacks'])) {
|
||||||
|
foreach ($element['#date_time_callbacks'] as $callback) {
|
||||||
|
if (function_exists($callback)) {
|
||||||
|
$callback($element, $form_state, $date);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $element;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validation callback for a datetime 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 form element whose value is being validated.
|
||||||
|
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||||
|
* The current state of the form.
|
||||||
|
* @param array $complete_form
|
||||||
|
* The complete form structure.
|
||||||
|
*/
|
||||||
|
public static function validateDatetime(&$element, FormStateInterface $form_state, &$complete_form) {
|
||||||
|
$input_exists = FALSE;
|
||||||
|
$input = NestedArray::getValue($form_state['values'], $element['#parents'], $input_exists);
|
||||||
|
if ($input_exists) {
|
||||||
|
|
||||||
|
$title = !empty($element['#title']) ? $element['#title'] : '';
|
||||||
|
$date_format = $element['#date_date_element'] != 'none' ? static::getHtml5DateFormat($element) : '';
|
||||||
|
$time_format = $element['#date_time_element'] != 'none' ? static::getHtml5TimeFormat($element) : '';
|
||||||
|
$format = trim($date_format . ' ' . $time_format);
|
||||||
|
|
||||||
|
// If there's empty input and the field is not required, set it to empty.
|
||||||
|
if (empty($input['date']) && empty($input['time']) && !$element['#required']) {
|
||||||
|
$form_state->setValueForElement($element, NULL);
|
||||||
|
}
|
||||||
|
// If there's empty input and the field is required, set an error. A
|
||||||
|
// reminder of the required format in the message provides a good UX.
|
||||||
|
elseif (empty($input['date']) && empty($input['time']) && $element['#required']) {
|
||||||
|
$form_state->setError($element, t('The %field date is required. Please enter a date in the format %format.', array('%field' => $title, '%format' => static::formatExample($format))));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// If the date is valid, set it.
|
||||||
|
$date = $input['object'];
|
||||||
|
if ($date instanceof DrupalDateTime && !$date->hasErrors()) {
|
||||||
|
$form_state->setValueForElement($element, $date);
|
||||||
|
}
|
||||||
|
// If the date is invalid, set an error. A reminder of the required
|
||||||
|
// format in the message provides a good UX.
|
||||||
|
else {
|
||||||
|
$form_state->setError($element, t('The %field date is invalid. Please enter a date in the format %format.', array('%field' => $title, '%format' => static::formatExample($format))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an example for a date format.
|
||||||
|
*
|
||||||
|
* This is centralized for a consistent method of creating these examples.
|
||||||
|
*
|
||||||
|
* @param string $format
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function formatExample($format) {
|
||||||
|
if (!static::$dateExample) {
|
||||||
|
static::$dateExample = new DrupalDateTime();
|
||||||
|
}
|
||||||
|
return static::$dateExample->format($format);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the right format for a HTML5 date element.
|
||||||
|
*
|
||||||
|
* The format is important because these elements will not work with any other
|
||||||
|
* format.
|
||||||
|
*
|
||||||
|
* @param string $element
|
||||||
|
* The $element to assess.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
* Returns the right format for the date element, or the original format
|
||||||
|
* if this is not a HTML5 element.
|
||||||
|
*/
|
||||||
|
protected static function getHtml5DateFormat($element) {
|
||||||
|
switch ($element['#date_date_element']) {
|
||||||
|
case 'date':
|
||||||
|
return DateFormat::load('html_date')->getPattern();
|
||||||
|
|
||||||
|
case 'datetime':
|
||||||
|
case 'datetime-local':
|
||||||
|
return DateFormat::load('html_datetime')->getPattern();
|
||||||
|
|
||||||
|
default:
|
||||||
|
return $element['#date_date_format'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the right format for a HTML5 time element.
|
||||||
|
*
|
||||||
|
* The format is important because these elements will not work with any other
|
||||||
|
* format.
|
||||||
|
*
|
||||||
|
* @param string $element
|
||||||
|
* The $element to assess.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
* Returns the right format for the time element, or the original format
|
||||||
|
* if this is not a HTML5 element.
|
||||||
|
*/
|
||||||
|
protected static function getHtml5TimeFormat($element) {
|
||||||
|
switch ($element['#date_time_element']) {
|
||||||
|
case 'time':
|
||||||
|
return DateFormat::load('html_time')->getPattern();
|
||||||
|
|
||||||
|
default:
|
||||||
|
return $element['#date_time_format'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -2,15 +2,14 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @file
|
* @file
|
||||||
* Contains \Drupal\system\Entity\DateFormat.
|
* Contains \Drupal\Core\Datetime\Entity\DateFormat.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Drupal\system\Entity;
|
namespace Drupal\Core\Datetime\Entity;
|
||||||
|
|
||||||
use Drupal\Core\Config\Entity\ConfigEntityBase;
|
use Drupal\Core\Config\Entity\ConfigEntityBase;
|
||||||
use Drupal\Core\Datetime\DrupalDateTime;
|
|
||||||
use Drupal\Core\Config\Entity\ConfigEntityInterface;
|
use Drupal\Core\Config\Entity\ConfigEntityInterface;
|
||||||
use Drupal\system\DateFormatInterface;
|
use Drupal\Core\Datetime\DateFormatInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines the Date Format configuration entity class.
|
* Defines the Date Format configuration entity class.
|
||||||
|
@ -20,22 +19,12 @@ use Drupal\system\DateFormatInterface;
|
||||||
* label = @Translation("Date format"),
|
* label = @Translation("Date format"),
|
||||||
* handlers = {
|
* handlers = {
|
||||||
* "access" = "Drupal\system\DateFormatAccessControlHandler",
|
* "access" = "Drupal\system\DateFormatAccessControlHandler",
|
||||||
* "list_builder" = "Drupal\system\DateFormatListBuilder",
|
|
||||||
* "form" = {
|
|
||||||
* "add" = "Drupal\system\Form\DateFormatAddForm",
|
|
||||||
* "edit" = "Drupal\system\Form\DateFormatEditForm",
|
|
||||||
* "delete" = "Drupal\system\Form\DateFormatDeleteForm"
|
|
||||||
* }
|
|
||||||
* },
|
* },
|
||||||
* entity_keys = {
|
* entity_keys = {
|
||||||
* "id" = "id",
|
* "id" = "id",
|
||||||
* "label" = "label"
|
* "label" = "label"
|
||||||
* },
|
* },
|
||||||
* admin_permission = "administer site configuration",
|
* admin_permission = "administer site configuration",
|
||||||
* links = {
|
|
||||||
* "delete-form" = "entity.date_format.delete_form",
|
|
||||||
* "edit-form" = "entity.date_format.edit_form"
|
|
||||||
* }
|
|
||||||
* )
|
* )
|
||||||
*/
|
*/
|
||||||
class DateFormat extends ConfigEntityBase implements DateFormatInterface {
|
class DateFormat extends ConfigEntityBase implements DateFormatInterface {
|
|
@ -0,0 +1,4 @@
|
||||||
|
@todo This must be here because DrupalKernel will only allow namespaces to
|
||||||
|
provide plugins if there is a Plugin subdirectory, and git does not allow
|
||||||
|
empty subdirectories. This file should be removed once
|
||||||
|
https://www.drupal.org/node/2309889 is fixed.
|
|
@ -397,15 +397,15 @@ class ConfigTranslationUiTest extends WebTestBase {
|
||||||
|
|
||||||
// Update translatable fields.
|
// Update translatable fields.
|
||||||
$edit = array(
|
$edit = array(
|
||||||
'config_names[system.date_format.' . $id . '][label][translation]' => $id . ' - FR',
|
'config_names[core.date_format.' . $id . '][label][translation]' => $id . ' - FR',
|
||||||
'config_names[system.date_format.' . $id . '][pattern][translation]' => 'D',
|
'config_names[core.date_format.' . $id . '][pattern][translation]' => 'D',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Save language specific version of form.
|
// Save language specific version of form.
|
||||||
$this->drupalPostForm($translation_page_url, $edit, t('Save translation'));
|
$this->drupalPostForm($translation_page_url, $edit, t('Save translation'));
|
||||||
|
|
||||||
// Get translation and check we've got the right value.
|
// Get translation and check we've got the right value.
|
||||||
$override = \Drupal::languageManager()->getLanguageConfigOverride('fr', 'system.date_format.' . $id);
|
$override = \Drupal::languageManager()->getLanguageConfigOverride('fr', 'core.date_format.' . $id);
|
||||||
$expected = array(
|
$expected = array(
|
||||||
'label' => $id . ' - FR',
|
'label' => $id . ' - FR',
|
||||||
'pattern' => 'D',
|
'pattern' => 'D',
|
||||||
|
|
|
@ -10,7 +10,7 @@ use Drupal\Core\Datetime\DrupalDateTime;
|
||||||
use Drupal\Core\Form\FormStateInterface;
|
use Drupal\Core\Form\FormStateInterface;
|
||||||
use Drupal\Core\Routing\RouteMatchInterface;
|
use Drupal\Core\Routing\RouteMatchInterface;
|
||||||
use Drupal\Core\Template\Attribute;
|
use Drupal\Core\Template\Attribute;
|
||||||
use Drupal\datetime\DateHelper;
|
use Drupal\Core\Datetime\Element\Datetime;
|
||||||
use Drupal\node\NodeInterface;
|
use Drupal\node\NodeInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -48,74 +48,6 @@ function datetime_help($route_name, RouteMatchInterface $route_match) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Implements hook_element_info().
|
|
||||||
*/
|
|
||||||
function datetime_element_info() {
|
|
||||||
$date_format = '';
|
|
||||||
$time_format = '';
|
|
||||||
// Date formats cannot be loaded during install or update.
|
|
||||||
if (!defined('MAINTENANCE_MODE')) {
|
|
||||||
if ($date_format_entity = entity_load('date_format', 'html_date')) {
|
|
||||||
$date_format = $date_format_entity->getPattern();
|
|
||||||
}
|
|
||||||
if ($time_format_entity = entity_load('date_format', 'html_time')) {
|
|
||||||
$time_format = $time_format_entity->getPattern();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$types['datetime'] = array(
|
|
||||||
'#input' => TRUE,
|
|
||||||
'#element_validate' => array('datetime_datetime_validate'),
|
|
||||||
'#process' => array(
|
|
||||||
'datetime_datetime_form_process',
|
|
||||||
array('Drupal\Core\Render\Element\RenderElement', 'processGroup'),
|
|
||||||
),
|
|
||||||
'#pre_render' => array(
|
|
||||||
array('Drupal\Core\Render\Element\RenderElement', 'preRenderGroup'),
|
|
||||||
),
|
|
||||||
'#theme' => 'datetime_form',
|
|
||||||
'#theme_wrappers' => array('datetime_wrapper'),
|
|
||||||
'#date_date_format' => $date_format,
|
|
||||||
'#date_date_element' => 'date',
|
|
||||||
'#date_date_callbacks' => array(),
|
|
||||||
'#date_time_format' => $time_format,
|
|
||||||
'#date_time_element' => 'time',
|
|
||||||
'#date_time_callbacks' => array(),
|
|
||||||
'#date_year_range' => '1900:2050',
|
|
||||||
'#date_increment' => 1,
|
|
||||||
'#date_timezone' => '',
|
|
||||||
);
|
|
||||||
$types['datelist'] = array(
|
|
||||||
'#input' => TRUE,
|
|
||||||
'#element_validate' => array('datetime_datelist_validate'),
|
|
||||||
'#process' => array('datetime_datelist_form_process'),
|
|
||||||
'#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' => '',
|
|
||||||
);
|
|
||||||
return $types;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implements hook_theme().
|
|
||||||
*/
|
|
||||||
function datetime_theme() {
|
|
||||||
return array(
|
|
||||||
'datetime_form' => array(
|
|
||||||
'template' => 'datetime-form',
|
|
||||||
'render element' => 'element',
|
|
||||||
),
|
|
||||||
'datetime_wrapper' => array(
|
|
||||||
'template' => 'datetime-wrapper',
|
|
||||||
'render element' => 'element',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validation callback for the datetime widget element.
|
* Validation callback for the datetime widget element.
|
||||||
*
|
*
|
||||||
|
@ -206,802 +138,6 @@ function datetime_date_default_time($date) {
|
||||||
$date->setTime(12, 0, 0);
|
$date->setTime(12, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepares variables for datetime form element templates.
|
|
||||||
*
|
|
||||||
* The datetime form element serves as a wrapper around the date element type,
|
|
||||||
* which creates a date and a time component for a date.
|
|
||||||
*
|
|
||||||
* Default template: datetime-form.html.twig.
|
|
||||||
*
|
|
||||||
* @param array $variables
|
|
||||||
* An associative array containing:
|
|
||||||
* - element: An associative array containing the properties of the element.
|
|
||||||
* Properties used: #title, #value, #options, #description, #required,
|
|
||||||
* #attributes.
|
|
||||||
*
|
|
||||||
* @see form_process_datetime()
|
|
||||||
*/
|
|
||||||
function template_preprocess_datetime_form(&$variables) {
|
|
||||||
$element = $variables['element'];
|
|
||||||
|
|
||||||
$variables['attributes'] = array();
|
|
||||||
if (isset($element['#id'])) {
|
|
||||||
$variables['attributes']['id'] = $element['#id'];
|
|
||||||
}
|
|
||||||
if (!empty($element['#attributes']['class'])) {
|
|
||||||
$variables['attributes']['class'] = (array) $element['#attributes']['class'];
|
|
||||||
}
|
|
||||||
$variables['attributes']['class'][] = 'container-inline';
|
|
||||||
|
|
||||||
$variables['content'] = $element;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepares variables for datetime form wrapper templates.
|
|
||||||
*
|
|
||||||
* Default template: datetime-wrapper.html.twig.
|
|
||||||
*
|
|
||||||
* @param array $variables
|
|
||||||
* An associative array containing:
|
|
||||||
* - element: An associative array containing the properties of the element.
|
|
||||||
* Properties used: #title, #children, #required, #attributes.
|
|
||||||
*/
|
|
||||||
function template_preprocess_datetime_wrapper(&$variables) {
|
|
||||||
$element = $variables['element'];
|
|
||||||
|
|
||||||
if (!empty($element['#title'])) {
|
|
||||||
$variables['title'] = $element['#title'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($element['#description'])) {
|
|
||||||
$variables['description'] = $element['#description'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$title_attributes = array('class' => array('label'));
|
|
||||||
// For required datetime fields a 'form-required' class is appended to the
|
|
||||||
// label attributes.
|
|
||||||
if (!empty($element['#required'])) {
|
|
||||||
$title_attributes['class'][] = 'form-required';
|
|
||||||
}
|
|
||||||
$variables['title_attributes'] = new Attribute($title_attributes);
|
|
||||||
$variables['content'] = $element['#children'];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Expands a datetime element type into date and/or time elements.
|
|
||||||
*
|
|
||||||
* All form elements are designed to have sane defaults so any or all can be
|
|
||||||
* omitted. Both the date and time components are configurable so they can be
|
|
||||||
* output as HTML5 datetime elements or not, as desired.
|
|
||||||
*
|
|
||||||
* Examples of possible configurations include:
|
|
||||||
* HTML5 date and time:
|
|
||||||
* #date_date_element = 'date';
|
|
||||||
* #date_time_element = 'time';
|
|
||||||
* HTML5 datetime:
|
|
||||||
* #date_date_element = 'datetime';
|
|
||||||
* #date_time_element = 'none';
|
|
||||||
* HTML5 time only:
|
|
||||||
* #date_date_element = 'none';
|
|
||||||
* #date_time_element = 'time'
|
|
||||||
* Non-HTML5:
|
|
||||||
* #date_date_element = 'text';
|
|
||||||
* #date_time_element = 'text';
|
|
||||||
*
|
|
||||||
* 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_date_format: A date format string that describes the format that
|
|
||||||
* should be displayed to the end user for the date. When using HTML5
|
|
||||||
* elements the format MUST use the appropriate HTML5 format for that
|
|
||||||
* element, no other format will work. See the format_date() function for a
|
|
||||||
* list of the possible formats and HTML5 standards for the HTML5
|
|
||||||
* requirements. Defaults to the right HTML5 format for the chosen element
|
|
||||||
* if a HTML5 element is used, otherwise defaults to
|
|
||||||
* entity_load('date_format', 'html_date')->getPattern().
|
|
||||||
* - #date_date_element: The date element. Options are:
|
|
||||||
* - datetime: Use the HTML5 datetime element type.
|
|
||||||
* - datetime-local: Use the HTML5 datetime-local element type.
|
|
||||||
* - date: Use the HTML5 date element type.
|
|
||||||
* - text: No HTML5 element, use a normal text field.
|
|
||||||
* - none: Do not display a date element.
|
|
||||||
* - #date_date_callbacks: Array of optional callbacks for the date element.
|
|
||||||
* Can be used to add a jQuery datepicker.
|
|
||||||
* - #date_time_element: The time element. Options are:
|
|
||||||
* - time: Use a HTML5 time element type.
|
|
||||||
* - text: No HTML5 element, use a normal text field.
|
|
||||||
* - none: Do not display a time element.
|
|
||||||
* - #date_time_format: A date format string that describes the format that
|
|
||||||
* should be displayed to the end user for the time. When using HTML5
|
|
||||||
* elements the format MUST use the appropriate HTML5 format for that
|
|
||||||
* element, no other format will work. See the format_date() function for
|
|
||||||
* a list of the possible formats and HTML5 standards for the HTML5
|
|
||||||
* requirements. Defaults to the right HTML5 format for the chosen element
|
|
||||||
* if a HTML5 element is used, otherwise defaults to
|
|
||||||
* entity_load('date_format', 'html_time')->getPattern().
|
|
||||||
* - #date_time_callbacks: An array of optional callbacks for the time
|
|
||||||
* element. Can be used to add a jQuery timepicker or an 'All day' checkbox.
|
|
||||||
* - #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. Used in jQueryUI datepicker year
|
|
||||||
* range and HTML5 min/max date settings. 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. Used for HTML5 step values and
|
|
||||||
* jQueryUI datepicker settings. 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' => 'datetime',
|
|
||||||
* '#default_value' => new DrupalDateTime('2000-01-01 00:00:00'),
|
|
||||||
* '#date_date_element' => 'date',
|
|
||||||
* '#date_time_element' => 'none',
|
|
||||||
* '#date_year_range' => '2010:+3',
|
|
||||||
* );
|
|
||||||
* @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.
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
* The form element whose value has been processed.
|
|
||||||
*/
|
|
||||||
function datetime_datetime_form_process($element, FormStateInterface $form_state) {
|
|
||||||
$format_settings = array();
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
if ($element['#date_date_element'] != 'none') {
|
|
||||||
|
|
||||||
$date_format = $element['#date_date_element'] != 'none' ? datetime_html5_format('date', $element) : '';
|
|
||||||
$date_value = !empty($date) ? $date->format($date_format, $format_settings) : $element['#value']['date'];
|
|
||||||
|
|
||||||
// Creating format examples on every individual date item is messy, and
|
|
||||||
// placeholders are invalid for HTML5 date and datetime, so an example
|
|
||||||
// format is appended to the title to appear in tooltips.
|
|
||||||
$extra_attributes = array(
|
|
||||||
'title' => t('Date (i.e. !format)', array('!format' => datetime_format_example($date_format))),
|
|
||||||
'type' => $element['#date_date_element'],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Adds the HTML5 date attributes.
|
|
||||||
if ($date instanceOf DrupalDateTime && !$date->hasErrors()) {
|
|
||||||
$html5_min = clone($date);
|
|
||||||
$range = datetime_range_years($element['#date_year_range'], $date);
|
|
||||||
$html5_min->setDate($range[0], 1, 1)->setTime(0, 0, 0);
|
|
||||||
$html5_max = clone($date);
|
|
||||||
$html5_max->setDate($range[1], 12, 31)->setTime(23, 59, 59);
|
|
||||||
|
|
||||||
$extra_attributes += array(
|
|
||||||
'min' => $html5_min->format($date_format, $format_settings),
|
|
||||||
'max' => $html5_max->format($date_format, $format_settings),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$element['date'] = array(
|
|
||||||
'#type' => 'date',
|
|
||||||
'#title' => t('Date'),
|
|
||||||
'#title_display' => 'invisible',
|
|
||||||
'#value' => $date_value,
|
|
||||||
'#attributes' => $element['#attributes'] + $extra_attributes,
|
|
||||||
'#required' => $element['#required'],
|
|
||||||
'#size' => max(12, strlen($element['#value']['date'])),
|
|
||||||
);
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($element['#date_time_element'] != 'none') {
|
|
||||||
|
|
||||||
$time_format = $element['#date_time_element'] != 'none' ? datetime_html5_format('time', $element) : '';
|
|
||||||
$time_value = !empty($date) ? $date->format($time_format, $format_settings) : $element['#value']['time'];
|
|
||||||
|
|
||||||
// Adds the HTML5 attributes.
|
|
||||||
$extra_attributes = array(
|
|
||||||
'title' =>t('Time (i.e. !format)', array('!format' => datetime_format_example($time_format))),
|
|
||||||
'type' => $element['#date_time_element'],
|
|
||||||
'step' => $element['#date_increment'],
|
|
||||||
);
|
|
||||||
$element['time'] = array(
|
|
||||||
'#type' => 'date',
|
|
||||||
'#title' => t('Time'),
|
|
||||||
'#title_display' => 'invisible',
|
|
||||||
'#value' => $time_value,
|
|
||||||
'#attributes' => $element['#attributes'] + $extra_attributes,
|
|
||||||
'#required' => $element['#required'],
|
|
||||||
'#size' => 12,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Allows custom callbacks to alter the element.
|
|
||||||
if (!empty($element['#date_time_callbacks'])) {
|
|
||||||
foreach ($element['#date_time_callbacks'] as $callback) {
|
|
||||||
if (function_exists($callback)) {
|
|
||||||
$callback($element, $form_state, $date);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $element;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Value callback for a datetime element.
|
|
||||||
*
|
|
||||||
* @param array $element
|
|
||||||
* The form element whose value is being populated.
|
|
||||||
* @param array $input
|
|
||||||
* (optional) The incoming input to populate the form element. If this is
|
|
||||||
* FALSE, the element's default value should be returned. Defaults to FALSE.
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
* The data that will appear in the $element_state['values'] collection for
|
|
||||||
* this element. Return nothing to use the default.
|
|
||||||
*/
|
|
||||||
function form_type_datetime_value($element, $input = FALSE) {
|
|
||||||
if ($input !== FALSE) {
|
|
||||||
$date_input = $element['#date_date_element'] != 'none' && !empty($input['date']) ? $input['date'] : '';
|
|
||||||
$time_input = $element['#date_time_element'] != 'none' && !empty($input['time']) ? $input['time'] : '';
|
|
||||||
$date_format = $element['#date_date_element'] != 'none' ? datetime_html5_format('date', $element) : '';
|
|
||||||
$time_format = $element['#date_time_element'] != 'none' ? datetime_html5_format('time', $element) : '';
|
|
||||||
$timezone = !empty($element['#date_timezone']) ? $element['#date_timezone'] : NULL;
|
|
||||||
|
|
||||||
// Seconds will be omitted in a post in case there's no entry.
|
|
||||||
if (!empty($time_input) && strlen($time_input) == 5) {
|
|
||||||
$time_input .= ':00';
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$date_time_format = trim($date_format . ' ' . $time_format);
|
|
||||||
$date_time_input = trim($date_input . ' ' . $time_input);
|
|
||||||
$date = DrupalDateTime::createFromFormat($date_time_format, $date_time_input, $timezone);
|
|
||||||
}
|
|
||||||
catch (\Exception $e) {
|
|
||||||
$date = NULL;
|
|
||||||
}
|
|
||||||
$input = array(
|
|
||||||
'date' => $date_input,
|
|
||||||
'time' => $time_input,
|
|
||||||
'object' => $date,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$date = $element['#default_value'];
|
|
||||||
if ($date instanceOf DrupalDateTime && !$date->hasErrors()) {
|
|
||||||
$input = array(
|
|
||||||
'date' => $date->format($element['#date_date_format']),
|
|
||||||
'time' => $date->format($element['#date_time_format']),
|
|
||||||
'object' => $date,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$input = array(
|
|
||||||
'date' => '',
|
|
||||||
'time' => '',
|
|
||||||
'object' => NULL,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $input;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validation callback for a datetime 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 form element whose value is being validated.
|
|
||||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
|
||||||
* The current state of the form.
|
|
||||||
*/
|
|
||||||
function datetime_datetime_validate($element, FormStateInterface $form_state) {
|
|
||||||
|
|
||||||
$input_exists = FALSE;
|
|
||||||
$input = NestedArray::getValue($form_state->getValues(), $element['#parents'], $input_exists);
|
|
||||||
if ($input_exists) {
|
|
||||||
|
|
||||||
$title = !empty($element['#title']) ? $element['#title'] : '';
|
|
||||||
$date_format = $element['#date_date_element'] != 'none' ? datetime_html5_format('date', $element) : '';
|
|
||||||
$time_format = $element['#date_time_element'] != 'none' ? datetime_html5_format('time', $element) : '';
|
|
||||||
$format = trim($date_format . ' ' . $time_format);
|
|
||||||
|
|
||||||
// If there's empty input and the field is not required, set it to empty.
|
|
||||||
if (empty($input['date']) && empty($input['time']) && !$element['#required']) {
|
|
||||||
form_set_value($element, NULL, $form_state);
|
|
||||||
}
|
|
||||||
// If there's empty input and the field is required, set an error. A
|
|
||||||
// reminder of the required format in the message provides a good UX.
|
|
||||||
elseif (empty($input['date']) && empty($input['time']) && $element['#required']) {
|
|
||||||
form_error($element, $form_state, t('The %field date is required. Please enter a date in the format %format.', array('%field' => $title, '%format' => datetime_format_example($format))));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// If the date is valid, set it.
|
|
||||||
$date = $input['object'];
|
|
||||||
if ($date instanceOf DrupalDateTime && !$date->hasErrors()) {
|
|
||||||
form_set_value($element, $date, $form_state);
|
|
||||||
}
|
|
||||||
// If the date is invalid, set an error. A reminder of the required
|
|
||||||
// format in the message provides a good UX.
|
|
||||||
else {
|
|
||||||
form_error($element, $form_state, t('The %field date is invalid. Please enter a date in the format %format.', array('%field' => $title, '%format' => datetime_format_example($format))));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the right format for a HTML5 date element.
|
|
||||||
*
|
|
||||||
* The format is important because these elements will not work with any other
|
|
||||||
* format.
|
|
||||||
*
|
|
||||||
* @param string $part
|
|
||||||
* The type of element format to retrieve.
|
|
||||||
* @param string $element
|
|
||||||
* The $element to assess.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
* Returns the right format for the type of element, or the original format
|
|
||||||
* if this is not a HTML5 element.
|
|
||||||
*/
|
|
||||||
function datetime_html5_format($part, $element) {
|
|
||||||
switch ($part) {
|
|
||||||
case 'date':
|
|
||||||
switch ($element['#date_date_element']) {
|
|
||||||
case 'date':
|
|
||||||
return entity_load('date_format', 'html_date')->getPattern();
|
|
||||||
|
|
||||||
case 'datetime':
|
|
||||||
case 'datetime-local':
|
|
||||||
return entity_load('date_format', 'html_datetime')->getPattern();
|
|
||||||
|
|
||||||
default:
|
|
||||||
return $element['#date_date_format'];
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'time':
|
|
||||||
switch ($element['#date_time_element']) {
|
|
||||||
case 'time':
|
|
||||||
return entity_load('date_format', 'html_time')->getPattern();
|
|
||||||
|
|
||||||
default:
|
|
||||||
return $element['#date_time_format'];
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an example for a date format.
|
|
||||||
*
|
|
||||||
* This is centralized for a consistent method of creating these examples.
|
|
||||||
*
|
|
||||||
* @param string $format
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
function datetime_format_example($format) {
|
|
||||||
$date = &drupal_static(__FUNCTION__);
|
|
||||||
if (empty($date)) {
|
|
||||||
$date = new DrupalDateTime();
|
|
||||||
}
|
|
||||||
return $date->format($format);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
function datetime_datelist_form_process($element, FormStateInterface $form_state) {
|
|
||||||
|
|
||||||
// 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 = datetime_range_years($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');
|
|
||||||
}
|
|
||||||
|
|
||||||
$default = !empty($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'],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Element value callback for datelist element.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* @param array $element
|
|
||||||
* The element being processed.
|
|
||||||
* @param array|false $input
|
|
||||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
|
||||||
* The current state of the form.
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
function form_type_datelist_value($element, $input = FALSE, FormStateInterface $form_state) {
|
|
||||||
$parts = $element['#date_part_order'];
|
|
||||||
$increment = $element['#date_increment'];
|
|
||||||
|
|
||||||
$date = NULL;
|
|
||||||
if ($input !== FALSE) {
|
|
||||||
$return = $input;
|
|
||||||
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()) {
|
|
||||||
date_increment_round($date, $increment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$return = array_fill_keys($parts, '');
|
|
||||||
if (!empty($element['#default_value'])) {
|
|
||||||
$date = $element['#default_value'];
|
|
||||||
if ($date instanceOf DrupalDateTime && !$date->hasErrors()) {
|
|
||||||
date_increment_round($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';
|
|
||||||
}
|
|
||||||
$return[$part] = $date->format($format);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$return['object'] = $date;
|
|
||||||
return $return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
function datetime_datelist_validate($element, FormStateInterface $form_state) {
|
|
||||||
$input_exists = FALSE;
|
|
||||||
$input = NestedArray::getValue($form_state->getValues(), $element['#parents'], $input_exists);
|
|
||||||
if ($input_exists) {
|
|
||||||
|
|
||||||
// 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_set_value($element, NULL, $form_state);
|
|
||||||
}
|
|
||||||
// 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_error($element, $form_state, t('The %field date is required.'));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// If the input is valid, set it.
|
|
||||||
$date = $input['object'];
|
|
||||||
if ($date instanceOf DrupalDateTime && !$date->hasErrors()) {
|
|
||||||
form_set_value($element, $date, $form_state);
|
|
||||||
}
|
|
||||||
// If the input is invalid, set an error.
|
|
||||||
else {
|
|
||||||
form_error($element, $form_state, t('The %field date is invalid.'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rounds minutes and seconds to nearest requested value.
|
|
||||||
*
|
|
||||||
* @param $date
|
|
||||||
*
|
|
||||||
* @param $increment
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
function date_increment_round(&$date, $increment) {
|
|
||||||
// Round minutes and seconds, if necessary.
|
|
||||||
if ($date instanceOf DrupalDateTime && $increment > 1) {
|
|
||||||
$day = intval(date_format($date, 'j'));
|
|
||||||
$hour = intval(date_format($date, 'H'));
|
|
||||||
$second = intval(round(intval(date_format($date, 's')) / $increment) * $increment);
|
|
||||||
$minute = intval(date_format($date, 'i'));
|
|
||||||
if ($second == 60) {
|
|
||||||
$minute += 1;
|
|
||||||
$second = 0;
|
|
||||||
}
|
|
||||||
$minute = intval(round($minute / $increment) * $increment);
|
|
||||||
if ($minute == 60) {
|
|
||||||
$hour += 1;
|
|
||||||
$minute = 0;
|
|
||||||
}
|
|
||||||
date_time_set($date, $hour, $minute, $second);
|
|
||||||
if ($hour == 24) {
|
|
||||||
$day += 1;
|
|
||||||
$year = date_format($date, 'Y');
|
|
||||||
$month = date_format($date, 'n');
|
|
||||||
date_date_set($date, $year, $month, $day);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $date;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Specifies the start and end year to use as a date range.
|
|
||||||
*
|
|
||||||
* Handles a string like -3:+3 or 2001:2010 to describe a dynamic range of
|
|
||||||
* minimum and maximum years to use in a date selector.
|
|
||||||
*
|
|
||||||
* Centers the range around the current year, if any, but expands 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.
|
|
||||||
*
|
|
||||||
* @param string $string
|
|
||||||
* A min and max year string like '-3:+1' or '2000:2010' or '2000:+3'.
|
|
||||||
* @param object $date
|
|
||||||
* (optional) A date object to test as a default value. Defaults to NULL.
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
* A numerically indexed array, containing the minimum and maximum year
|
|
||||||
* described by this pattern.
|
|
||||||
*/
|
|
||||||
function datetime_range_years($string, $date = NULL) {
|
|
||||||
|
|
||||||
$this_year = date_format(new DrupalDateTime(), 'Y');
|
|
||||||
list($min_year, $max_year) = explode(':', $string);
|
|
||||||
|
|
||||||
// Valid patterns would be -5:+5, 0:+1, 2008:2010.
|
|
||||||
$plus_pattern = '@[\+|\-][0-9]{1,4}@';
|
|
||||||
$year_pattern = '@^[0-9]{4}@';
|
|
||||||
if (!preg_match($year_pattern, $min_year, $matches)) {
|
|
||||||
if (preg_match($plus_pattern, $min_year, $matches)) {
|
|
||||||
$min_year = $this_year + $matches[0];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$min_year = $this_year;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!preg_match($year_pattern, $max_year, $matches)) {
|
|
||||||
if (preg_match($plus_pattern, $max_year, $matches)) {
|
|
||||||
$max_year = $this_year + $matches[0];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$max_year = $this_year;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// We expect the $min year to be less than the $max year. Some custom values
|
|
||||||
// for -99:+99 might not obey that.
|
|
||||||
if ($min_year > $max_year) {
|
|
||||||
$temp = $max_year;
|
|
||||||
$max_year = $min_year;
|
|
||||||
$min_year = $temp;
|
|
||||||
}
|
|
||||||
// If there is a current value, stretch the range to include it.
|
|
||||||
$value_year = $date instanceOf DrupalDateTime ? $date->format('Y') : '';
|
|
||||||
if (!empty($value_year)) {
|
|
||||||
$min_year = min($value_year, $min_year);
|
|
||||||
$max_year = max($value_year, $max_year);
|
|
||||||
}
|
|
||||||
return array($min_year, $max_year);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements hook_form_BASE_FORM_ID_alter() for node forms.
|
* Implements hook_form_BASE_FORM_ID_alter() for node forms.
|
||||||
*/
|
*/
|
||||||
|
@ -1010,7 +146,7 @@ function datetime_form_node_form_alter(&$form, FormStateInterface $form_state, $
|
||||||
$form['created']['#type'] = 'datetime';
|
$form['created']['#type'] = 'datetime';
|
||||||
$date_format = entity_load('date_format', 'html_date')->getPattern();
|
$date_format = entity_load('date_format', 'html_date')->getPattern();
|
||||||
$time_format = entity_load('date_format', 'html_time')->getPattern();
|
$time_format = entity_load('date_format', 'html_time')->getPattern();
|
||||||
$form['created']['#description'] = t('Format: %format. Leave blank to use the time of form submission.', array('%format' => datetime_format_example($date_format . ' ' . $time_format)));
|
$form['created']['#description'] = t('Format: %format. Leave blank to use the time of form submission.', array('%format' => Datetime::formatExample($date_format . ' ' . $time_format)));
|
||||||
unset($form['created']['#maxlength']);
|
unset($form['created']['#maxlength']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -110,7 +110,7 @@ class LocaleConfigTranslationTest extends WebTestBase {
|
||||||
);
|
);
|
||||||
$this->drupalPostForm('admin/config/regional/translate', $edit, t('Save translations'));
|
$this->drupalPostForm('admin/config/regional/translate', $edit, t('Save translations'));
|
||||||
|
|
||||||
$wrapper = $this->container->get('locale.config.typed')->get('system.date_format.medium');
|
$wrapper = $this->container->get('locale.config.typed')->get('core.date_format.medium');
|
||||||
|
|
||||||
// Get translation and check we've only got the site name.
|
// Get translation and check we've only got the site name.
|
||||||
$translation = $wrapper->getTranslation($langcode);
|
$translation = $wrapper->getTranslation($langcode);
|
||||||
|
|
|
@ -19,7 +19,7 @@ class EntityDateFormat extends EntityConfigBase {
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*
|
*
|
||||||
* @param \Drupal\system\DateFormatInterface $entity
|
* @param \Drupal\Core\Datetime\DateFormatInterface $entity
|
||||||
* The date entity.
|
* The date entity.
|
||||||
*/
|
*/
|
||||||
protected function updateEntityProperty(EntityInterface $entity, array $parents, $value) {
|
protected function updateEntityProperty(EntityInterface $entity, array $parents, $value) {
|
||||||
|
|
|
@ -9,7 +9,7 @@ use Drupal\migrate_drupal\Tests\MigrateDrupalTestBase;
|
||||||
use Drupal\Core\Database\Database;
|
use Drupal\Core\Database\Database;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Upgrade date formats to system.date_format.*.yml.
|
* Upgrade date formats to core.date_format.*.yml.
|
||||||
*
|
*
|
||||||
* @group migrate_drupal
|
* @group migrate_drupal
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -114,26 +114,6 @@ system.date:
|
||||||
type: boolean
|
type: boolean
|
||||||
label: 'Remind users at login if their time zone is not set'
|
label: 'Remind users at login if their time zone is not set'
|
||||||
|
|
||||||
system.date_format.*:
|
|
||||||
type: config_entity
|
|
||||||
label: 'Date format'
|
|
||||||
mapping:
|
|
||||||
id:
|
|
||||||
type: string
|
|
||||||
label: 'ID'
|
|
||||||
label:
|
|
||||||
type: label
|
|
||||||
label: 'Label'
|
|
||||||
locked:
|
|
||||||
type: boolean
|
|
||||||
label: 'Locked'
|
|
||||||
pattern:
|
|
||||||
type: date_format
|
|
||||||
label: 'PHP date format'
|
|
||||||
langcode:
|
|
||||||
type: string
|
|
||||||
label: 'Default language'
|
|
||||||
|
|
||||||
system.diff:
|
system.diff:
|
||||||
type: mapping
|
type: mapping
|
||||||
label: 'Diff settings'
|
label: 'Diff settings'
|
||||||
|
|
|
@ -186,14 +186,14 @@ class ThemeHandlerTest extends DrupalUnitTestBase {
|
||||||
$this->themeHandler()->disable(array($name));
|
$this->themeHandler()->disable(array($name));
|
||||||
|
|
||||||
$this->assertIdentical($this->config("$name.settings")->get('base'), 'only');
|
$this->assertIdentical($this->config("$name.settings")->get('base'), 'only');
|
||||||
$this->assertIdentical($this->config('system.date_format.fancy')->get('label'), 'Fancy date');
|
$this->assertIdentical($this->config('core.date_format.fancy')->get('label'), 'Fancy date');
|
||||||
|
|
||||||
// Default configuration never overwrites custom configuration, so just
|
// Default configuration never overwrites custom configuration, so just
|
||||||
// changing values in existing configuration will cause ConfigInstaller to
|
// changing values in existing configuration will cause ConfigInstaller to
|
||||||
// simply skip those files. To ensure that no default configuration is
|
// simply skip those files. To ensure that no default configuration is
|
||||||
// re-imported, the custom configuration has to be deleted.
|
// re-imported, the custom configuration has to be deleted.
|
||||||
$this->configStorage()->delete("$name.settings");
|
$this->configStorage()->delete("$name.settings");
|
||||||
$this->configStorage()->delete('system.date_format.fancy');
|
$this->configStorage()->delete('core.date_format.fancy');
|
||||||
// Reflect direct storage operations in ConfigFactory.
|
// Reflect direct storage operations in ConfigFactory.
|
||||||
$this->container->get('config.factory')->reset();
|
$this->container->get('config.factory')->reset();
|
||||||
|
|
||||||
|
@ -206,7 +206,7 @@ class ThemeHandlerTest extends DrupalUnitTestBase {
|
||||||
$this->assertEqual(array_keys(system_list('theme')), array_keys($themes));
|
$this->assertEqual(array_keys(system_list('theme')), array_keys($themes));
|
||||||
|
|
||||||
$this->assertFalse($this->config("$name.settings")->get());
|
$this->assertFalse($this->config("$name.settings")->get());
|
||||||
$this->assertNull($this->config('system.date_format.fancy')->get('label'));
|
$this->assertNull($this->config('core.date_format.fancy')->get('label'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1277,6 +1277,20 @@ function system_retrieve_file($url, $destination = NULL, $managed = FALSE, $repl
|
||||||
return $local;
|
return $local;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements hook_entity_type_build().
|
||||||
|
*/
|
||||||
|
function system_entity_type_build(array &$entity_types) {
|
||||||
|
/** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
|
||||||
|
$entity_types['date_format']
|
||||||
|
->setFormClass('add', 'Drupal\system\Form\DateFormatAddForm')
|
||||||
|
->setFormClass('edit', 'Drupal\system\Form\DateFormatEditForm')
|
||||||
|
->setFormClass('delete', 'Drupal\system\Form\DateFormatDeleteForm')
|
||||||
|
->setListBuilderClass('Drupal\system\DateFormatListBuilder')
|
||||||
|
->setLinkTemplate('edit-form', 'entity.date_format.edit_form')
|
||||||
|
->setLinkTemplate('delete-form', 'entity.date_format.delete_form');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements hook_page_alter().
|
* Implements hook_page_alter().
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue