Commit f6b4d4a2 authored by Dries's avatar Dries

Issue #501428 by KarenS, swentel, tim.plunkett, webchick, effulgentsia, nod_,...

Issue #501428 by KarenS, swentel, tim.plunkett, webchick, effulgentsia, nod_, scor, Lars Toomre: Added Date and time field type in core.
parent 8edd4427
......@@ -1954,6 +1954,30 @@ function _format_date_callback(array $matches = NULL, $new_langcode = NULL) {
return $cache[$langcode][$code][$string];
}
/**
* Retrieves the correct datetime format type for this system.
*
* This value is sometimes required when the format type needs to be determined
* before a date can be created.
*
* @return string
* A string as defined in \DrupalComponent\Datetime\DateTimePlus.php: either
* 'intl' or 'php', depending on whether IntlDateFormatter is available.
*/
function datetime_default_format_type() {
static $drupal_static_fast;
if (!isset($drupal_static_fast)) {
$drupal_static_fast['format_type'] = &drupal_static(__FUNCTION__);
}
$format_type = &$drupal_static_fast['format_type'];
if (!isset($format_type)) {
$date = new DrupalDateTime();
$format_type = $date->canUseIntl() ? DrupalDateTime::INTL : DrupalDateTime::PHP;
}
return $format_type;
}
/**
* @} End of "defgroup format".
*/
......
......@@ -9,6 +9,7 @@
use Drupal\Core\Form\FormInterface;
use Drupal\Core\Database\Database;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Utility\Color;
/**
......@@ -1728,8 +1729,8 @@ function form_error(&$element, $message = '') {
* - $element['#process']: An array of functions called after user input has
* been mapped to the element's #value property. These functions can be used
* to dynamically add child elements: for example, for the 'date' element
* type, one of the functions in this array is form_process_date(), which adds
* the individual 'year', 'month', 'day', etc. child elements. These functions
* type, one of the functions in this array is form_process_datetime(), which adds
* the individual 'date', and 'time'. child elements. These functions
* can also be used to set additional properties or implement special logic
* other than adding child elements: for example, for the 'details' element
* type, one of the functions in this array is form_process_details(), which
......@@ -3031,118 +3032,29 @@ function password_confirm_validate($element, &$element_state) {
}
/**
* Returns HTML for a date selection form element.
* Returns HTML for an #date form element.
*
* @param $variables
* Supports HTML5 types of 'date', 'datetime', 'datetime-local', and 'time'.
* Falls back to a plain textfield. Used as a sub-element by the datetime
* element type.
*
* @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.
* #attributes, #id, #name, #type, #min, #max, #step, #value, #size.
*
* @ingroup themeable
*/
function theme_date($variables) {
$element = $variables['element'];
$attributes = array();
if (isset($element['#id'])) {
$attributes['id'] = $element['#id'];
}
if (!empty($element['#attributes']['class'])) {
$attributes['class'] = (array) $element['#attributes']['class'];
}
$attributes['class'][] = 'container-inline';
return '<div' . new Attribute($attributes) . '>' . drupal_render_children($element) . '</div>';
}
/**
* Expands a date element into year, month, and day select elements.
*/
function form_process_date($element) {
// Default to current date
if (empty($element['#value'])) {
$element['#value'] = array(
'day' => format_date(REQUEST_TIME, 'custom', 'j'),
'month' => format_date(REQUEST_TIME, 'custom', 'n'),
'year' => format_date(REQUEST_TIME, 'custom', 'Y'),
);
if (empty($element['attribute']['type'])) {
$element['attribute']['type'] = 'date';
}
element_set_attributes($element, array('id', 'name', 'type', 'min', 'max', 'step', 'value', 'size'));
_form_set_attributes($element, array('form-' . $element['attribute']['type']));
$element['#tree'] = TRUE;
// Determine the order of day, month, year in the site's chosen date format.
$format = config('system.date')->get('formats.short.pattern');
$format = $format['php'];
$sort = array();
$sort['day'] = max(strpos($format, 'd'), strpos($format, 'j'));
$sort['month'] = max(strpos($format, 'm'), strpos($format, 'M'));
$sort['year'] = strpos($format, 'Y');
asort($sort);
$order = array_keys($sort);
// Output multi-selector for date.
foreach ($order as $type) {
switch ($type) {
case 'day':
$options = drupal_map_assoc(range(1, 31));
$title = t('Day');
break;
case 'month':
$options = drupal_map_assoc(range(1, 12), 'map_month');
$title = t('Month');
break;
case 'year':
$options = drupal_map_assoc(range(1900, 2050));
$title = t('Year');
break;
}
$element[$type] = array(
'#type' => 'select',
'#title' => $title,
'#title_display' => 'invisible',
'#value' => $element['#value'][$type],
'#attributes' => $element['#attributes'],
'#options' => $options,
);
}
return $element;
}
/**
* Validates the date type to prevent invalid dates (e.g., February 30, 2006).
*/
function date_validate($element) {
if (!checkdate($element['#value']['month'], $element['#value']['day'], $element['#value']['year'])) {
form_error($element, t('The specified date is invalid.'));
}
}
/**
* Renders a month name for display.
*
* Callback for drupal_map_assoc() within form_process_date().
*/
function map_month($month) {
$months = &drupal_static(__FUNCTION__, array(
1 => 'Jan',
2 => 'Feb',
3 => 'Mar',
4 => 'Apr',
5 => 'May',
6 => 'Jun',
7 => 'Jul',
8 => 'Aug',
9 => 'Sep',
10 => 'Oct',
11 => 'Nov',
12 => 'Dec',
));
return t($months[$month]);
return '<input' . new Attribute($element['#attributes']) . ' />';
}
/**
......
......@@ -1564,6 +1564,12 @@ function theme_disable($theme_list) {
/**
* Preprocess variables for theme_datetime().
*
* @param array $variables
* An associative array possbily containing:
* - attributes['timestamp']:
* - timestamp:
* - text:
*/
function template_preprocess_datetime(&$variables) {
// Format the 'datetime' attribute based on the timestamp.
......@@ -1585,6 +1591,7 @@ function template_preprocess_datetime(&$variables) {
$variables['html'] = FALSE;
}
}
$variables['attributes'] = new Attribute($variables['attributes']);
}
/**
......
......@@ -4,5 +4,6 @@ package = Core
version = VERSION
core = 8.x
dependencies[] = node
dependencies[] = datetime
dependencies[] = text
configure = admin/content/comment
......@@ -61,7 +61,7 @@ public function form(array $form, array &$form_state, EntityInterface $comment)
if ($is_admin) {
$author = $comment->name->value;
$status = (isset($comment->status->value) ? $comment->status->value : COMMENT_NOT_PUBLISHED);
$date = (!empty($comment->date) ? $comment->date : format_date($comment->created->value, 'custom', 'Y-m-d H:i O'));
$date = (!empty($comment->date) ? $comment->date : new DrupalDateTime($comment->created->value));
}
else {
if ($user->uid) {
......@@ -117,10 +117,9 @@ public function form(array $form, array &$form_state, EntityInterface $comment)
// Add administrative comment publishing options.
$form['author']['date'] = array(
'#type' => 'textfield',
'#type' => 'datetime',
'#title' => t('Authored on'),
'#default_value' => $date,
'#maxlength' => 25,
'#size' => 20,
'#access' => $is_admin,
);
......@@ -212,8 +211,8 @@ public function validate(array $form, array &$form_state) {
$account = user_load_by_name($form_state['values']['name']);
$form_state['values']['uid'] = $account ? $account->uid : 0;
$date = new DrupalDateTime(!empty($form_state['values']['date']) ? $form_state['values']['date'] : 'now');
if ($date->hasErrors()) {
$date = $form_state['values']['date'];
if ($date instanceOf DrupalDateTime && $date->hasErrors()) {
form_set_error('date', t('You have to specify a valid date.'));
}
if ($form_state['values']['name'] && !$form_state['values']['is_anonymous'] && !$account) {
......@@ -244,8 +243,12 @@ public function validate(array $form, array &$form_state) {
*/
public function buildEntity(array $form, array &$form_state) {
$comment = parent::buildEntity($form, $form_state);
$date = new DrupalDateTime(!empty($form_state['values']['date']) ? $form_state['values']['date'] : 'now');
$comment->created->value = $date->getTimestamp();
if (!empty($form_state['values']['date']) && $form_state['values']['date'] instanceOf DrupalDateTime) {
$comment->created->value = $form_state['values']['date']->getTimestamp();
}
else {
$comment->created->value = REQUEST_TIME;
}
$comment->changed->value = REQUEST_TIME;
return $comment;
}
......
......@@ -7,6 +7,8 @@
namespace Drupal\comment\Tests;
use Drupal\Core\Datetime\DrupalDateTime;
/**
* Tests previewing comments.
*/
......@@ -86,13 +88,16 @@ function testCommentEditPreviewSave() {
$this->setCommentSettings('comment_default_mode', COMMENT_MODE_THREADED, 'Comment paging changed.');
$edit = array();
$date = new DrupalDateTime('2008-03-02 17:23');
$edit['subject'] = $this->randomName(8);
$edit['comment_body[' . $langcode . '][0][value]'] = $this->randomName(16);
$edit['name'] = $web_user->name;
$edit['date'] = '2008-03-02 17:23 +0300';
$raw_date = strtotime($edit['date']);
$edit['date[date]'] = $date->format('Y-m-d');
$edit['date[time]'] = $date->format('H:i:s');
$raw_date = $date->getTimestamp();
$expected_text_date = format_date($raw_date);
$expected_form_date = format_date($raw_date, 'custom', 'Y-m-d H:i O');
$expected_form_date = $date->format('Y-m-d');
$expected_form_time = $date->format('H:i:s');
$comment = $this->postComment($this->node, $edit['subject'], $edit['comment_body[' . $langcode . '][0][value]'], TRUE);
$this->drupalPost('comment/' . $comment->id() . '/edit', $edit, t('Preview'));
......@@ -107,7 +112,8 @@ function testCommentEditPreviewSave() {
$this->assertFieldByName('subject', $edit['subject'], 'Subject field displayed.');
$this->assertFieldByName('comment_body[' . $langcode . '][0][value]', $edit['comment_body[' . $langcode . '][0][value]'], 'Comment field displayed.');
$this->assertFieldByName('name', $edit['name'], 'Author field displayed.');
$this->assertFieldByName('date', $edit['date'], 'Date field displayed.');
$this->assertFieldByName('date[date]', $edit['date[date]'], 'Date field displayed.');
$this->assertFieldByName('date[time]', $edit['date[time]'], 'Time field displayed.');
// Check that saving a comment produces a success message.
$this->drupalPost('comment/' . $comment->id() . '/edit', $edit, t('Save'));
......@@ -118,14 +124,16 @@ function testCommentEditPreviewSave() {
$this->assertFieldByName('subject', $edit['subject'], 'Subject field displayed.');
$this->assertFieldByName('comment_body[' . $langcode . '][0][value]', $edit['comment_body[' . $langcode . '][0][value]'], 'Comment field displayed.');
$this->assertFieldByName('name', $edit['name'], 'Author field displayed.');
$this->assertFieldByName('date', $expected_form_date, 'Date field displayed.');
$this->assertFieldByName('date[date]', $expected_form_date, 'Date field displayed.');
$this->assertFieldByName('date[time]', $expected_form_time, 'Time field displayed.');
// Submit the form using the displayed values.
$displayed = array();
$displayed['subject'] = (string) current($this->xpath("//input[@id='edit-subject']/@value"));
$displayed['comment_body[' . $langcode . '][0][value]'] = (string) current($this->xpath("//textarea[@id='edit-comment-body-" . $langcode . "-0-value']"));
$displayed['name'] = (string) current($this->xpath("//input[@id='edit-name']/@value"));
$displayed['date'] = (string) current($this->xpath("//input[@id='edit-date']/@value"));
$displayed['date[date]'] = (string) current($this->xpath("//input[@id='edit-date-date']/@value"));
$displayed['date[time]'] = (string) current($this->xpath("//input[@id='edit-date-time']/@value"));
$this->drupalPost('comment/' . $comment->id() . '/edit', $displayed, t('Save'));
// Check that the saved comment is still correct.
......
......@@ -20,7 +20,7 @@ abstract class CommentTestBase extends WebTestBase {
*
* @var array
*/
public static $modules = array('comment', 'node', 'history', 'field_ui');
public static $modules = array('comment', 'node', 'history', 'field_ui', 'datetime');
/**
* An administrative user with permission to configure comment settings.
......
name = Datetime
description = Defines datetime form elements and a datetime field type.
package = Core
version = VERSION
core = 8.x
dependencies[] = field
<?php
/**
* @file
* Install, update and uninstall functions for the Datetime module.
*/
/**
* Implements hook_field_schema().
*/
function datetime_field_schema($field) {
$db_columns = array();
$db_columns['value'] = array(
'description' => 'The date value',
'type' => 'varchar',
'length' => 20,
'not null' => FALSE,
);
$indexes = array(
'value' => array('value'),
);
return array('columns' => $db_columns, 'indexes' => $indexes);
}
/**
* Install the new to D8 Datetime module.
*
* As part of adding this new module to Drupal 8, the Datetime namespace is now
* reserved for this module. This is a possible conflict with a popular contrib
* field DateTime that existed in D7. Hence, any Datetime fields that may have
* existed prior to D8 need to renamed for later upgrade by contrib modules like
* the Date module.
*/
function datetime_install() {
db_update('field_config')
->fields(array(
'type' => 'datetime_old',
))
->condition('type', 'datetime')
->execute();
}
This diff is collapsed.
<?php
/**
* @file
* Contains \Drupal\datetime\DateHelper.
*
* Lots of helpful functions for use in massaging dates, specific to the the
* Gregorian calendar system. The values include both translated and
* untranslated values.
*
* Untranslated values are useful as array keys and as css identifiers, and
* should be listed in English.
*
* Translated values are useful for display to the user. All values that need
* translation should be hard-coded and wrapped in t() so the translation system
* will be able to process them.
*/
namespace Drupal\datetime;
use Drupal\Core\Datetime\DrupalDateTime;
/**
* Defines Gregorian Calendar date values.
*/
class DateHelper {
/**
* Constructs an untranslated array of month names.
*
* @return array
* An array of month names.
*/
public static function monthNamesUntranslated() {
// Force the key to use the correct month value, rather than
// starting with zero.
return array(
1 => 'January',
2 => 'February',
3 => 'March',
4 => 'April',
5 => 'May',
6 => 'June',
7 => 'July',
8 => 'August',
9 => 'September',
10 => 'October',
11 => 'November',
12 => 'December',
);
}
/**
* Constructs an untranslated array of abbreviated month names.
*
* @return array
* An array of month names.
*/
public static function monthNamesAbbrUntranslated() {
// Force the key to use the correct month value, rather than
// starting with zero.
return array(
1 => 'Jan',
2 => 'Feb',
3 => 'Mar',
4 => 'Apr',
5 => 'May',
6 => 'Jun',
7 => 'Jul',
8 => 'Aug',
9 => 'Sep',
10 => 'Oct',
11 => 'Nov',
12 => 'Dec',
);
}
/**
* Returns a translated array of month names.
*
* @param bool $required
* (optional) If FALSE, the returned array will include a blank value.
* Defaults to FALSE.
*
* @return array
* An array of month names.
*/
public static function monthNames($required = FALSE) {
// Force the key to use the correct month value, rather than
// starting with zero.
$monthnames = array(
1 => t('January', array(), array('context' => 'Long month name')),
2 => t('February', array(), array('context' => 'Long month name')),
3 => t('March', array(), array('context' => 'Long month name')),
4 => t('April', array(), array('context' => 'Long month name')),
5 => t('May', array(), array('context' => 'Long month name')),
6 => t('June', array(), array('context' => 'Long month name')),
7 => t('July', array(), array('context' => 'Long month name')),
8 => t('August', array(), array('context' => 'Long month name')),
9 => t('September', array(), array('context' => 'Long month name')),
10 => t('October', array(), array('context' => 'Long month name')),
11 => t('November', array(), array('context' => 'Long month name')),
12 => t('December', array(), array('context' => 'Long month name')),
);
$none = array('' => '');
return !$required ? $none + $monthnames : $monthnames;
}
/**
* Constructs a translated array of month name abbreviations
*
* @param bool $required
* (optional) If FALSE, the returned array will include a blank value.
* Defaults to FALSE.
*
* @return array
* An array of month abbreviations.
*/
public static function monthNamesAbbr($required = FALSE) {
// Force the key to use the correct month value, rather than
// starting with zero.
$monthnames = array(
1 => t('Jan'),
2 => t('Feb'),
3 => t('Mar'),
4 => t('Apr'),
5 => t('May'),
6 => t('Jun'),
7 => t('Jul'),
8 => t('Aug'),
9 => t('Sep'),
10 => t('Oct'),
11 => t('Nov'),
12 => t('Dec'),
);
$none = array('' => '');
return !$required ? $none + $monthnames : $monthnames;
}
/**
* Constructs an untranslated array of week days.
*
* @return array
* An array of week day names
*/
public static function weekDaysUntranslated() {
return array(
'Sunday',
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday',
);
}
/**
* Returns a translated array of week names.
*
* @param bool $required
* (optional) If FALSE, the returned array will include a blank value.
* Defaults to FALSE.
*
* @return array
* An array of week day names
*/
public static function weekDays($required = FALSE) {
$weekdays = array(
t('Sunday'),
t('Monday'),
t('Tuesday'),
t('Wednesday'),
t('Thursday'),
t('Friday'),
t('Saturday'),
);
$none = array('' => '');
return !$required ? $none + $weekdays : $weekdays;
}
/**
* Constructs a translated array of week day abbreviations.
*
* @param bool $required
* (optional) If FALSE, the returned array will include a blank value.
* Defaults to FALSE.
*
* @return array
* An array of week day abbreviations
*/
public static function weekDaysAbbr($required = FALSE) {
$weekdays = array(
t('Sun', array(), array('context' => 'Sunday abbreviation')),
t('Mon', array(), array('context' => 'Monday abbreviation')),
t('Tue', array(), array('context' => 'Tuesday abbreviation')),
t('Wed', array(), array('context' => 'Wednesday abbreviation')),
t('Thu', array(), array('context' => 'Thursday abbreviation')),
t('Fri', array(), array('context' => 'Friday abbreviation')),
t('Sat', array(), array('context' => 'Saturday abbreviation')),
);
$none = array('' => '');
return !$required ? $none + $weekdays : $weekdays;
}
/**
* Constructs a translated array of 2-letter week day abbreviations.
*
* @param bool $required
* (optional) If FALSE, the returned array will include a blank value.
* Defaults to FALSE.
*
* @return array
* An array of week day 2 letter abbreviations
*/
public static function weekDaysAbbr2($required = FALSE) {
$weekdays = array(
t('Su', array(), array('context' => 'Sunday 2 letter abbreviation')),
t('Mo', array(), array('context' => 'Monday 2 letter abbreviation')),
t('Tu', array(), array('context' => 'Tuesday 2 letter abbreviation')),
t('We', array(), array('context' => 'Wednesday 2 letter abbreviation')),
t('Th', array(), array('context' => 'Thursday 2 letter abbreviation')),
t('Fr', array(), array('context' => 'Friday 2 letter abbreviation')),
t('Sa', array(), array('context' => 'Saturday 2 letter abbreviation')),
);
$none = array('' => '');
return !$required ? $none + $weekdays : $weekdays;
}
/**
* Constructs a translated array of 1-letter week day abbreviations.
*
* @param bool $required
* (optional) If FALSE, the returned array will include a blank value.
* Defaults to FALSE.
*