diff --git a/docs/en/02_Developer_Guides/03_Forms/Field_types/02_DateField.md b/docs/en/02_Developer_Guides/03_Forms/Field_types/02_DateField.md index 40d761fb0..8870e5fea 100644 --- a/docs/en/02_Developer_Guides/03_Forms/Field_types/02_DateField.md +++ b/docs/en/02_Developer_Guides/03_Forms/Field_types/02_DateField.md @@ -40,19 +40,19 @@ A custom date format for a [api:DateField] can be provided through `setDateForma DateField::create('MyDate')->setDateFormat('dd-MM-yyyy');
-The formats are based on [CLDR format](http://userguide.icu-project.org/formatparse/datetime). +The formats are based on [ICU format](http://www.icu-project.org/apiref/icu4c/classSimpleDateFormat.html#details).
## Min and Max Dates Sets the minimum and maximum allowed date values using the `min` and `max` configuration settings (in ISO format or -strtotime()). +`strtotime()`). :::php DateField::create('MyDate') ->setMinDate('-7 days') - ->setMaxDate'2012-12-31') + ->setMaxDate('2012-12-31') ## Separate Day / Month / Year Fields @@ -66,33 +66,21 @@ HTML5 placeholders 'day', 'month' and 'year' are enabled by default. Any custom date format settings will be ignored. -## Calendar Picker +## Date Picker and HTML5 support -The following setting will add a Calendar to a single DateField, using the jQuery UI DatePicker widget. +The field can be used as a [HTML5 input date type](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date) +(with `type=date`) by calling `setHTML5(true)`. :::php DateField::create('MyDate') - ->setShowCalendar(true); + ->setHTML5(true); -The jQuery date picker will support most custom locale formats (if left as default). -If setting an explicit date format via setDateFormat() then the below table of supported -characters should be used. +In browsers [supporting HTML5 date inputs](caniuse.com/#feat=input-datetime), +this will cause a localised date picker to appear for users. +In this mode, the field will be forced to present and save ISO 8601 date formats (`y-MM-dd`), +since the browser takes care of converting to/from a localised presentation. -It is recommended to use numeric format, as `MMM` or `MMMM` month names may not always pass validation. - -Constant | xxxxx --------- | ----- -d | numeric day of the month (without leading zero) -dd | numeric day of the month (with leading zero) -EEE | dayname, abbreviated -EEEE | dayname -M | numeric month of the year (without leading zero) -MM | numeric month of the year (with leading zero) -MMM | monthname, abbreviated -MMMM | monthname -y | year (4 digits) -yy | year (2 digits) -yyyy | year (4 digits) +Browsers without support receive an `` based polyfill. ## Formatting Hints diff --git a/docs/en/02_Developer_Guides/13_i18n/index.md b/docs/en/02_Developer_Guides/13_i18n/index.md index 46023817f..f5363cab9 100644 --- a/docs/en/02_Developer_Guides/13_i18n/index.md +++ b/docs/en/02_Developer_Guides/13_i18n/index.md @@ -71,18 +71,24 @@ and default alignment of paragraphs and tables to browsers. ### Date and time formats -Formats can be set globally in the i18n class. These settings are currently only picked up by the CMS, you'll need -to write your own logic for any frontend output. +Formats can be set globally in the i18n class. +You can use these settings for your own view logic. :::php Config::inst()->update('i18n', 'date_format', 'dd.MM.YYYY'); Config::inst()->update('i18n', 'time_format', 'HH:mm'); -Most localization routines in SilverStripe use the [Zend_Date API](http://framework.zend.com/manual/1.12/en/zend.date.overview.html). -This means all formats are defined in -[ISO date format](http://framework.zend.com/manual/1.12/en/zend.date.constants.html), +Localization in SilverStripe uses PHP's [intl extension](http://php.net/intl). +Formats for it's [IntlDateFormatter](http://php.net/manual/en/class.intldateformatter.php) +are defined in [ICU format](http://www.icu-project.org/apiref/icu4c/classSimpleDateFormat.html#details), not PHP's built-in [date()](http://nz.php.net/manual/en/function.date.php). +These settings are not used for CMS presentation. +Users can choose their own locale, which determines the date format +that gets presented to them. Currently this is a mix of PHP defaults (for readonly `DateField` and `TimeField`), +browser defaults (for `DateField` on browsers supporting HTML5), and [Moment.JS](http://momentjs.com/) +client-side logic (for `DateField` polyfills and other readonly dates and times). + ### Language Names SilverStripe comes with a built-in list of common languages, listed by locale and region. diff --git a/docs/en/04_Changelogs/4.0.0.md b/docs/en/04_Changelogs/4.0.0.md index 5db6936df..bb03f9099 100644 --- a/docs/en/04_Changelogs/4.0.0.md +++ b/docs/en/04_Changelogs/4.0.0.md @@ -398,6 +398,18 @@ In templates this can also be invoked as below: <%t MyObject.PLURALS 'An item|{count} items' count=$Count %> +#### Removed Member.DateFormat and Member.TimeFormat database settings + +We're using [native HTML5 date and time pickers](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date) +in `DateField` and `TimeField` now ([discussion](https://github.com/silverstripe/silverstripe-framework/issues/6626)), +where the browser localises the output based on the browser/system preferences. +In this context it no longer makes sense to give users control over their own +date and time formats in their CMS profile. + +`Member->getDateFormat()` and `Member->getTimeFormat()` still exist, and default to +the [IntlDateFormatter defaults](http://php.net/manual/en/class.intldateformatter.php) for the selected locale. + + #### New asset storage mechanism File system has been abstracted into an abstract interface. By default, the out of the box filesystem @@ -1495,19 +1507,27 @@ New `DatetimeField` methods replace `getConfig()` / `setConfig()`: New `DateField` methods replace `getConfig()` / `setConfig()`: -* `getShowCalendar()` / `setShowCalendar()` -* `getDateFormat()` / `setShowCalendar()` +* `getDateFormat()` / `setDateFormat()` * `getMinDate()` / `setMinDate()` * `getMaxDate()` / `setMaxDate()` -* `getPlaceholders()` / `setPlaceholders()` -* `getClientLocale` / `setClientLocale` * `getLocale()` / `setLocale()` -* option `dmyfields` is now superceded with an `SeparatedDateField` class + +The `DateField` has changed behavior: + +* `DateField` no longer provides a jQuery UI date picker, + and uses [HTML5 date pickers](https://www.wufoo.com/html5/types/4-date.html) instead. + Use `setUseHTML()` to activate this mode (instead of `setConfig('showcalendar', true)`). +* `DateField` provides an optional polyfill for + [browsers without HTML5 date picker support](http://caniuse.com/#feat=input-datetime) +* The `dmyfields` option is now superceded with an `SeparatedDateField` class. +* `getPlaceholders()` / `setPlaceholders()` moved to a new `SeparatedDateField` class +* `getClientLocale` / `setClientLocale` have been removed (handled by `DateField->locale` and browser settings) New `TimeField` methods replace `getConfig()` / `setConfig()` * `getTimeFormat()` / `setTimeFormat()` * `getLocale()` / `setLocale()` +* `getClientConfig()` has been removed (in favour of `setHTML5()`) #### Template and Form Removed API diff --git a/src/Forms/DateField.php b/src/Forms/DateField.php index 4df37bb3f..6f8eef379 100644 --- a/src/Forms/DateField.php +++ b/src/Forms/DateField.php @@ -115,25 +115,27 @@ class DateField extends TextField protected $rawValue = null; /** - * Check if calendar should be shown on the frontend + * Use HTML5-based input fields (and force ISO 8601 date formats). * + * @var bool + */ + protected $html5 = false; + + /** * @return bool */ - public function getShowCalendar() + public function getHTML5() { - return $this->showCalendar; + return $this->html5; } /** - * Set if calendar should be shown on the frontend. - * @internal WARNING: Experimental and volatile API. - * - * @param bool $show + * @param boolean $bool * @return $this */ - public function setShowCalendar($show) + public function setHTML5($bool) { - $this->showCalendar = $show; + $this->html5 = $bool; return $this; } @@ -209,6 +211,7 @@ class DateField extends TextField /** * Get date formatter with the standard locale / date format * + * @throws \LogicException * @return IntlDateFormatter */ protected function getFormatter() @@ -219,8 +222,20 @@ class DateField extends TextField IntlDateFormatter::NONE ); - // Don't invoke getDateFormat() directly to avoid infinite loop - if ($this->dateFormat) { + $isoFormat = 'y-MM-dd'; + + if ($this->dateFormat && $this->getHTML5() && $this->dateFormat !== $isoFormat) { + throw new \LogicException(sprintf( + 'Can\'t use a custom dateFormat value with $html5=true (needs to be %s)', + $isoFormat + )); + } + + if ($this->getHTML5()) { + // Browsers expect ISO 8601 dates, localisation is handled on the client + $formatter->setPattern($isoFormat); + } elseif ($this->dateFormat) { + // Don't invoke getDateFormat() directly to avoid infinite loop $ok = $formatter->setPattern($this->dateFormat); if (!$ok) { throw new InvalidArgumentException("Invalid date format {$this->dateFormat}"); @@ -236,59 +251,38 @@ class DateField extends TextField */ protected function getISO8601Formatter() { + $locale = i18n::config()->uninherited('default_locale'); $formatter = IntlDateFormatter::create( i18n::config()->uninherited('default_locale'), IntlDateFormatter::MEDIUM, IntlDateFormatter::NONE ); $formatter->setLenient(false); - // CLDR iso8601 date. + // CLDR ISO 8601 date. $formatter->setPattern('y-MM-dd'); return $formatter; } public function FieldHolder($properties = array()) { - return $this->renderWithClientView(function () use ($properties) { - return parent::FieldHolder($properties); - }); - } - - public function SmallFieldHolder($properties = array()) - { - return $this->renderWithClientView(function () use ($properties) { - return parent::SmallFieldHolder($properties); - }); - } - - /** - * Generate field with client view enabled - * - * @param callable $callback - * @return string - */ - protected function renderWithClientView($callback) - { - $clientView = null; - if ($this->getShowCalendar()) { - $clientView = $this->getClientView(); - $clientView->onBeforeRender(); + if ($this->getHTML5()) { + // Browsers expect ISO 8601 dates, localisation is handled on the client + $this->setDateFormat('y-MM-dd'); } - $html = $callback(); - if ($clientView) { - $html = $clientView->onAfterRender($html); - } - return $html; + + return parent::FieldHolder($properties); } public function getAttributes() { $attributes = parent::getAttributes(); - // Merge with client config - $config = $this->getClientConfig(); - foreach ($config as $key => $value) { - $attributes["data-{$key}"] = $value; + $attributes['lang'] = i18n::convert_rfc1766($this->getLocale()); + + if ($this->getHTML5()) { + $attributes['type'] = 'date'; + $attributes['min'] = $this->getMinDate(); + $attributes['max'] = $this->getMaxDate(); } return $attributes; @@ -438,29 +432,6 @@ class DateField extends TextField return $this; } - /** - * Get locale code for client-side. Will default to getLocale() if omitted. - * - * @return string - */ - public function getClientLocale() - { - if ($this->clientLocale) { - return $this->clientLocale; - } - return $this->getLocale(); - } - - /** - * @param string $clientLocale - * @return DateField - */ - public function setClientLocale($clientLocale) - { - $this->clientLocale = $clientLocale; - return $this; - } - public function getSchemaValidation() { $rules = parent::getSchemaValidation(); @@ -504,35 +475,6 @@ class DateField extends TextField return $this; } - /** - * Get client data properties for this field - * - * @return array - */ - public function getClientConfig() - { - $view = $this->getClientView(); - $config = [ - 'showcalendar' => $this->getShowCalendar() ? 'true' : null, - 'date-format' => $view->getDateFormat(), // https://api.jqueryui.com/datepicker/#option-dateFormat - 'locale' => $view->getLocale(), - ]; - - // Format min/maxDate in format expected by jquery datepicker - $min = $this->getMinDate(); - if ($min) { - // https://api.jqueryui.com/datepicker/#option-minDate - $config['min-date'] = $this->iso8601ToLocalised($min); - } - $max = $this->getMaxDate(); - if ($max) { - // https://api.jqueryui.com/datepicker/#option-maxDate - $config['max-date'] = $this->iso8601ToLocalised($max); - } - - return $config; - } - /** * Convert date localised in the current locale to ISO 8601 date * @@ -599,11 +541,4 @@ class DateField extends TextField return $formatter->format($timestamp); } - /** - * @return DateField_View_JQuery - */ - protected function getClientView() - { - return DateField_View_JQuery::create($this); - } } diff --git a/src/Forms/DateField_View_JQuery.php b/src/Forms/DateField_View_JQuery.php deleted file mode 100644 index b00539b80..000000000 --- a/src/Forms/DateField_View_JQuery.php +++ /dev/null @@ -1,197 +0,0 @@ - 'en-GB', - 'en_US' => 'en', - 'en_NZ' => 'en-GB', - 'fr_CH' => 'fr', - 'pt_BR' => 'pt-BR', - 'sr_SR' => 'sr-SR', - 'zh_CN' => 'zh-CN', - 'zh_HK' => 'zh-HK', - 'zh_TW' => 'zh-TW', - ); - - /** - * @param DateField $field - */ - public function __construct($field) - { - $this->field = $field; - - // Health check - if (!$this->localePath('en')) { - throw new InvalidArgumentException("Missing jquery config"); - } - } - - /** - * @return DateField - */ - public function getField() - { - return $this->field; - } - - /** - * Get path to localisation file for a given locale, if it exists - * - * @param string $lang - * @return string Relative path to file, or null if it isn't available - */ - protected function localePath($lang) - { - $path = ADMIN_THIRDPARTY_DIR . "/jquery-ui/datepicker/i18n/jquery.ui.datepicker-{$lang}.js"; - if (file_exists(BASE_PATH . '/' . $path)) { - return $path; - } - return null; - } - - public function onBeforeRender() - { - } - - /** - * @param String $html - * @return string - */ - public function onAfterRender($html) - { - if ($this->getField()->getShowCalendar()) { - // Load config for this locale if available - $locale = $this->getLocale(); - $localeFile = $this->localePath($locale); - if ($localeFile) { - Requirements::javascript($localeFile); - } - } - - return $html; - } - - /** - * Determines which language to use for jQuery UI, which - * can be different from the value set in i18n. - * - * @return string - */ - public function getLocale() - { - $locale = $this->getField()->getClientLocale(); - - // Check standard mappings - $map = Config::inst()->get(__CLASS__, 'locale_map'); - if (array_key_exists($locale, $map)) { - return $map[$locale]; - } - - // Fall back to default lang (meaning "en_US" turns into "en") - return i18n::getData()->langFromLocale($locale); - } - - /** - * Convert iso to jquery UI date format. - * Needs to be consistent with Zend formatting, otherwise validation will fail. - * Removes all time settings like hour/minute/second from the format. - * See http://docs.jquery.com/UI/Datepicker/formatDate - * From http://userguide.icu-project.org/formatparse/datetime - * - * @param string $format - * @return string - */ - public static function convert_iso_to_jquery_format($format) - { - $convert = array( - '/([^d])d([^d])/' => '$1d$2', - '/^d([^d])/' => 'd$1', - '/([^d])d$/' => '$1d', - '/dd/' => 'dd', - '/SS/' => '', - '/eee/' => 'd', - '/e/' => 'N', - '/D/' => '', - '/EEEE/' => 'DD', - '/EEE/' => 'D', - '/w/' => '', - // make single "M" lowercase - '/([^M])M([^M])/' => '$1m$2', - // make single "M" at start of line lowercase - '/^M([^M])/' => 'm$1', - // make single "M" at end of line lowercase - '/([^M])M$/' => '$1m', - // match exactly three capital Ms not preceeded or followed by an M - '/(? 'M', - // match exactly two capital Ms not preceeded or followed by an M - '/(? 'mm', - // match four capital Ms (maximum allowed) - '/MMMM/' => 'MM', - '/l/' => '', - '/YYYY/' => 'yy', - '/yyyy/' => 'yy', - // See http://open.silverstripe.org/ticket/7669 - '/y{1,3}/' => 'yy', - '/a/' => '', - '/B/' => '', - '/hh/' => '', - '/h/' => '', - '/([^H])H([^H])/' => '', - '/^H([^H])/' => '', - '/([^H])H$/' => '', - '/HH/' => '', - // '/mm/' => '', - '/ss/' => '', - '/zzzz/' => '', - '/I/' => '', - '/ZZZZ/' => '', - '/Z/' => '', - '/z/' => '', - '/X/' => '', - '/r/' => '', - '/U/' => '', - ); - $patterns = array_keys($convert); - $replacements = array_values($convert); - - return preg_replace($patterns, $replacements, $format); - } - - /** - * Get client date format - * - * @return string - */ - public function getDateFormat() - { - return static::convert_iso_to_jquery_format($this->getField()->getDateFormat()); - } -} diff --git a/src/Forms/DatetimeField.php b/src/Forms/DatetimeField.php index 4190e2d3e..b47933faa 100644 --- a/src/Forms/DatetimeField.php +++ b/src/Forms/DatetimeField.php @@ -13,6 +13,10 @@ use SilverStripe\i18n\i18n; * If you want to save into {@link Date} or {@link Time} columns, * please instanciate the fields separately. * + * This field does not implement the HTML5 field, + * but can use date and time HTML5 inputs separately (through {@link DateField->setHTML5()} + * and {@link TimeField->setHTML5()}. + * * # Configuration * * Individual options are configured either on the DatetimeField, or on individual diff --git a/src/Forms/TimeField.php b/src/Forms/TimeField.php index ee5427510..4f63004b0 100644 --- a/src/Forms/TimeField.php +++ b/src/Forms/TimeField.php @@ -57,6 +57,31 @@ class TimeField extends TextField */ protected $timezone = null; + /** + * Use HTML5-based input fields (and force ISO 8601 date formats). + * + * @var bool + */ + protected $html5 = false; + + /** + * @return bool + */ + public function getHTML5() + { + return $this->html5; + } + + /** + * @param boolean $bool + * @return $this + */ + public function setHTML5($bool) + { + $this->html5 = $bool; + return $this; + } + /** * Get time format in CLDR standard format * @@ -140,8 +165,20 @@ class TimeField extends TextField $this->getTimezone() ); - // Don't invoke getDateFormat() directly to avoid infinite loop - if ($this->timeFormat) { + $isoFormat = 'HH:mm:ss'; + + if ($this->timeFormat && $this->getHTML5() && $this->timeFormat !== $isoFormat) { + throw new \LogicException(sprintf( + 'Can\'t use a custom timeFormat value with $html5=true (needs to be %s)', + $isoFormat + )); + } + + if ($this->getHTML5()) { + // Browsers expect ISO 8601 times, localisation is handled on the client + $formatter->setPattern($isoFormat); + // Don't invoke getTimeFormat() directly to avoid infinite loop + } elseif ($this->timeFormat) { $ok = $formatter->setPattern($this->timeFormat); if (!$ok) { throw new InvalidArgumentException("Invalid time format {$this->timeFormat}"); @@ -164,35 +201,21 @@ class TimeField extends TextField date_default_timezone_get() // Default to server timezone ); $formatter->setLenient(false); - // CLDR iso8601 time + // ISO 8601 time // Note we omit timezone from this format, and we assume server TZ always. $formatter->setPattern('HH:mm:ss'); return $formatter; } - public function getAttribute($name) + public function getAttributes() { $attributes = parent::getAttributes(); - // Merge with client config - $config = $this->getClientConfig(); - foreach ($config as $key => $value) { - $attributes["data-{$key}"] = $value; + if ($this->getHTML5()) { + $attributes['type'] = 'time'; } - return $attributes; - } - /** - * Get client config options for this field - * - * @return array - */ - public function getClientConfig() - { - return [ - // @todo - Support javascript time picker - 'timeformat' => $this->getTimeFormat(), - ]; + return $attributes; } public function Type() @@ -337,9 +360,19 @@ class TimeField extends TextField $fromFormatter = $this->getFormatter(); $toFormatter = $this->getISO8601Formatter(); $timestamp = $fromFormatter->parse($time); + + // Try to parse time without seconds, since that's a valid HTML5 submission format + // See https://html.spec.whatwg.org/multipage/infrastructure.html#times + if ($timestamp === false && $this->setHTML5(true)) { + $fromFormatter->setPattern('HH:mm'); + $timestamp = $fromFormatter->parse($time); + } + + // If timestamp still can't be detected, we've got an invalid time if ($timestamp === false) { return null; } + return $toFormatter->format($timestamp); } diff --git a/src/ORM/FieldType/DBDate.php b/src/ORM/FieldType/DBDate.php index 1e1fd26e8..4eeb6a0a7 100644 --- a/src/ORM/FieldType/DBDate.php +++ b/src/ORM/FieldType/DBDate.php @@ -512,17 +512,7 @@ class DBDate extends DBField public function scaffoldFormField($title = null, $params = null) { $field = DateField::create($this->name, $title); - $format = $field->getDateFormat(); - - // Show formatting hints for better usability - $now = DBDatetime::now()->Format($format); - $field->setDescription(_t( - 'FormField.EXAMPLE', - 'e.g. {format}', - 'Example format', - [ 'format' => $now ] - )); - $field->setAttribute('placeholder', $format); + $field->setHTML5(true); return $field; } diff --git a/src/ORM/FieldType/DBTime.php b/src/ORM/FieldType/DBTime.php index d6eac7f06..629d4da1a 100644 --- a/src/ORM/FieldType/DBTime.php +++ b/src/ORM/FieldType/DBTime.php @@ -142,17 +142,8 @@ class DBTime extends DBField public function scaffoldFormField($title = null, $params = null) { $field = TimeField::create($this->name, $title); - $format = $field->getTimeFormat(); + $field->setHTML5(true); - // Show formatting hints for better usability - $now = DBDatetime::now()->Format($format); - $field->setDescription(_t( - 'FormField.Example', - 'e.g. {format}', - 'Example format', - [ 'format' => $now ] - )); - $field->setAttribute('placeholder', $format); return $field; } diff --git a/src/Security/Member.php b/src/Security/Member.php index e7090a706..1009e4a24 100644 --- a/src/Security/Member.php +++ b/src/Security/Member.php @@ -19,7 +19,6 @@ use SilverStripe\Forms\DropdownField; use SilverStripe\Forms\FieldList; use SilverStripe\Forms\HTMLEditor\HTMLEditorConfig; use SilverStripe\Forms\ListboxField; -use SilverStripe\Forms\MemberDatetimeOptionsetField; use SilverStripe\i18n\i18n; use SilverStripe\MSSQL\MSSQLDatabase; use SilverStripe\ORM\ArrayList; @@ -81,9 +80,6 @@ class Member extends DataObject implements TemplateGlobalProvider 'Locale' => 'Varchar(6)', // handled in registerFailedLogin(), only used if $lock_out_after_incorrect_logins is set 'FailedLoginCount' => 'Int', - // In ISO format - 'DateFormat' => 'Varchar(30)', - 'TimeFormat' => 'Varchar(30)', ); private static $belongs_many_many = array( @@ -1315,19 +1311,16 @@ class Member extends DataObject implements TemplateGlobalProvider } /** - * Override the default getter for DateFormat so the - * default format for the user's locale is used - * if the user has not defined their own. + * Return the date format based on the user's chosen locale, + * falling back to the default format defined by the {@link i18n.get_locale()} setting. * * @return string ISO date format */ public function getDateFormat() { - $format = $this->getField('DateFormat'); - if ($format) { - return $format; - } - return $this->getDefaultDateFormat(); + $format = $this->getDefaultDateFormat(); + $this->extend('updateDateFormat', $format); + return $format; } /** @@ -1343,19 +1336,17 @@ class Member extends DataObject implements TemplateGlobalProvider } /** - * Override the default getter for TimeFormat so the - * default format for the user's locale is used - * if the user has not defined their own. + * Return the time format based on the user's chosen locale, + * falling back to the default format defined by the {@link i18n.get_locale()} setting. * * @return string ISO date format */ public function getTimeFormat() { - $timeFormat = $this->getField('TimeFormat'); - if ($timeFormat) { - return $timeFormat; - } - return $this->getDefaultTimeFormat(); + $format = $this->getDefaultTimeFormat(); + $this->extend('updateTimeFormat', $format); + + return $format; } //---------------------------------------------------------------------// @@ -1592,61 +1583,11 @@ class Member extends DataObject implements TemplateGlobalProvider if ($permissionsTab) { $permissionsTab->addExtraClass('readonly'); } - - // Date format selecter - $mainFields->push( - $dateFormatField = new MemberDatetimeOptionsetField( - 'DateFormat', - $this->fieldLabel('DateFormat'), - $this->getDateFormats() - ) - ); - $formatClass = get_class($dateFormatField); - $dateFormatField->setValue($this->DateFormat); - $dateTemplate = SSViewer::get_templates_by_class($formatClass, '_description_date', $formatClass); - $dateFormatField->setDescriptionTemplate($dateTemplate); - - // Time format selector - $mainFields->push( - $timeFormatField = new MemberDatetimeOptionsetField( - 'TimeFormat', - $this->fieldLabel('TimeFormat'), - $this->getTimeFormats() - ) - ); - $timeFormatField->setValue($this->TimeFormat); - $timeTemplate = SSViewer::get_templates_by_class($formatClass, '_description_time', $formatClass); - $timeFormatField->setDescriptionTemplate($timeTemplate); }); return parent::getCMSFields(); } - /** - * Get list of date formats with example values - * - * @return array - */ - protected function getDateFormats() - { - $defaultDateFormat = $this->getDefaultDateFormat(); - $formats = [ - 'MMM d, y' => null, - 'yyyy/MM/dd' => null, - 'MM/dd/y' => null, - 'dd/MM/y' => null, - ]; - unset($formats[$defaultDateFormat]); - $formats[$defaultDateFormat] = null; - // Fill in each format with example - foreach (array_keys($formats) as $format) { - $formats[$format] = DBDatetime::now()->Format($format); - } - // Mark default format - $formats[$defaultDateFormat] .= sprintf(' (%s)', _t('Member.DefaultDateTime', 'default')); - return $formats; - } - /** * @return string */ @@ -1674,30 +1615,6 @@ class Member extends DataObject implements TemplateGlobalProvider return $defaultTimeFormat; } - - /** - * Get list of date formats with example values - * - * @return array - */ - protected function getTimeFormats() - { - $defaultTimeFormat = $this->getDefaultTimeFormat(); - $formats = [ - 'h:mm a' => null, - 'H:mm' => null, - ]; - unset($formats[$defaultTimeFormat]); - $formats[$defaultTimeFormat] = null; - // Fill in each format with example - foreach (array_keys($formats) as $format) { - $formats[$format] = DBDatetime::now()->Format($format); - } - // Mark default format - $formats[$defaultTimeFormat] .= sprintf(' (%s)', _t('Member.DefaultDateTime', 'default')); - return $formats; - } - /** * @param bool $includerelations Indicate if the labels returned include relation fields * @return array @@ -1714,8 +1631,6 @@ class Member extends DataObject implements TemplateGlobalProvider $labels['PasswordExpiry'] = _t('Member.db_PasswordExpiry', 'Password Expiry Date', 'Password expiry date'); $labels['LockedOutUntil'] = _t('Member.db_LockedOutUntil', 'Locked out until', 'Security related date'); $labels['Locale'] = _t('Member.db_Locale', 'Interface Locale'); - $labels['DateFormat'] = _t('Member.DATEFORMAT', 'Date format'); - $labels['TimeFormat'] = _t('Member.TIMEFORMAT', 'Time format'); if ($includerelations) { $labels['Groups'] = _t( 'Member.belongs_many_many_Groups', diff --git a/src/i18n/i18n.php b/src/i18n/i18n.php index ee7c5fcea..732eb3922 100644 --- a/src/i18n/i18n.php +++ b/src/i18n/i18n.php @@ -85,12 +85,20 @@ class i18n implements TemplateGlobalProvider private static $default_locale = 'en_US'; /** + * System-wide date format. Will be overruled for CMS UI display + * by the format defaults inferred from the browser as well as + * any user-specific locale preferences. + * * @config * @var string */ private static $date_format = 'yyyy-MM-dd'; /** + * System-wide time format. Will be overruled for CMS UI display + * by the format defaults inferred from the browser as well as + * any user-specific locale preferences. + * * @config * @var string */ diff --git a/tests/php/Forms/DateFieldTest.php b/tests/php/Forms/DateFieldTest.php index fbfe18e89..29eba129b 100644 --- a/tests/php/Forms/DateFieldTest.php +++ b/tests/php/Forms/DateFieldTest.php @@ -259,4 +259,16 @@ class DateFieldTest extends SapphireTest "Even if input value hasn't got leading 0's in it we still get the correct data value" ); } + + /** + * @expectedException \LogicException + */ + public function testHtml5WithCustomFormatThrowsException() + { + $dateField = new DateField('Date', 'Date'); + $dateField->setValue('2010-03-31'); + $dateField->setHTML5(true); + $dateField->setDateFormat('d/M/y'); + $dateField->Value(); + } } diff --git a/tests/php/Forms/DatefieldViewJQueryTest.php b/tests/php/Forms/DatefieldViewJQueryTest.php deleted file mode 100644 index 83b7a931a..000000000 --- a/tests/php/Forms/DatefieldViewJQueryTest.php +++ /dev/null @@ -1,47 +0,0 @@ -assertEquals( - 'M d, yy', - DateField_View_JQuery::convert_iso_to_jquery_format('MMM d, yyyy') - ); - - $this->assertEquals( - 'd/mm/yy', - DateField_View_JQuery::convert_iso_to_jquery_format('d/MM/yyyy') - ); - - $this->assertEquals( - 'dd.m.yy', - DateField_View_JQuery::convert_iso_to_jquery_format('dd.M.yyyy'), - 'Month, no leading zero' - ); - - $this->assertEquals( - 'dd.mm.yy', - DateField_View_JQuery::convert_iso_to_jquery_format('dd.MM.yyyy'), - 'Month, two digit' - ); - - $this->assertEquals( - 'dd.M.yy', - DateField_View_JQuery::convert_iso_to_jquery_format('dd.MMM.yyyy'), - 'Abbreviated month name' - ); - - $this->assertEquals( - 'dd.MM.yy', - DateField_View_JQuery::convert_iso_to_jquery_format('dd.MMMM.yyyy'), - 'Full month name' - ); - } -} diff --git a/tests/php/Forms/MemberDatetimeOptionsetFieldTest.php b/tests/php/Forms/MemberDatetimeOptionsetFieldTest.php deleted file mode 100644 index 5755b4fa8..000000000 --- a/tests/php/Forms/MemberDatetimeOptionsetFieldTest.php +++ /dev/null @@ -1,177 +0,0 @@ -getDefaultDateFormat(); - $dateFormatMap = array( - 'yyyy-MM-dd' => DBDatetime::now()->Format('yyyy-MM-dd'), - 'yyyy/MM/dd' => DBDatetime::now()->Format('yyyy/MM/dd'), - 'MM/dd/yyyy' => DBDatetime::now()->Format('MM/dd/yyyy'), - 'dd/MM/yyyy' => DBDatetime::now()->Format('dd/MM/yyyy'), - ); - $dateFormatMap[$defaultDateFormat] = DBDatetime::now()->Format($defaultDateFormat) . ' (default)'; - $field = new MemberDatetimeOptionsetField( - 'DateFormat', - 'Date format', - $dateFormatMap - ); - $field->setValue($member->getDateFormat()); - return $field; - } - - /** - * @param Member $member - * @return MemberDatetimeOptionsetField - */ - protected function createTimeFormatFieldForMember($member) - { - $defaultTimeFormat = $member->getDefaultTimeFormat(); - $timeFormatMap = array( - 'h:mm a' => DBDatetime::now()->Format('h:mm a'), - 'H:mm' => DBDatetime::now()->Format('H:mm'), - ); - $timeFormatMap[$defaultTimeFormat] = DBDatetime::now()->Format($defaultTimeFormat) . ' (default)'; - $field = new MemberDatetimeOptionsetField( - 'TimeFormat', - 'Time format', - $timeFormatMap - ); - $field->setValue($member->getTimeFormat()); - return $field; - } - - public function testDateFormatDefaultCheckedInFormField() - { - /** @var Member $member */ - $member = $this->objFromFixture(Member::class, 'noformatmember'); - $field = $this->createDateFormatFieldForMember($member); - /** @skipUpgrade */ - $field->setForm( - new Form( - new Controller(), - 'Form', - new FieldList(), - new FieldList() - ) - ); // fake form - // `MMM d, y` is default format for default locale (en_US) - $parser = new CSSContentParser($field->Field()); - $xmlArr = $parser->getBySelector('#Form_Form_DateFormat_MMM_d_y'); - $this->assertEquals('checked', (string) $xmlArr[0]['checked']); - } - - public function testTimeFormatDefaultCheckedInFormField() - { - /** @var Member $member */ - $member = $this->objFromFixture(Member::class, 'noformatmember'); - $field = $this->createTimeFormatFieldForMember($member); - /** @skipUpgrade */ - $field->setForm( - new Form( - new Controller(), - 'Form', - new FieldList(), - new FieldList() - ) - ); // fake form - // `h:mm:ss a` is the default for en_US locale - $parser = new CSSContentParser($field->Field()); - $xmlArr = $parser->getBySelector('#Form_Form_TimeFormat_h:mm:ss_a'); - $this->assertEquals('checked', (string) $xmlArr[0]['checked']); - } - - public function testDateFormatChosenIsCheckedInFormField() - { - /** @var Member $member */ - $member = $this->objFromFixture(Member::class, 'noformatmember'); - $member->setField('DateFormat', 'MM/dd/yyyy'); - $field = $this->createDateFormatFieldForMember($member); - /** @skipUpgrade */ - $field->setForm( - new Form( - new Controller(), - 'Form', - new FieldList(), - new FieldList() - ) - ); // fake form - $parser = new CSSContentParser($field->Field()); - $xmlArr = $parser->getBySelector('#Form_Form_DateFormat_MM_dd_yyyy'); - $this->assertEquals('checked', (string) $xmlArr[0]['checked']); - } - - public function testDateFormatCustomFormatAppearsInCustomInputInField() - { - /** @var Member $member */ - $member = $this->objFromFixture(Member::class, 'noformatmember'); - $member->setField('DateFormat', 'dd MM yy'); - $field = $this->createDateFormatFieldForMember($member); - /** @skipUpgrade */ - $field->setForm( - new Form( - new Controller(), - 'Form', - new FieldList(), - new FieldList() - ) - ); // fake form - $parser = new CSSContentParser($field->Field()); - $xmlInputArr = $parser->getBySelector('.valcustom input'); - $this->assertEquals('checked', (string) $xmlInputArr[0]['checked']); - $this->assertEquals('dd MM yy', (string) $xmlInputArr[1]['value']); - } - - public function testDateFormValid() - { - $field = new MemberDatetimeOptionsetField('DateFormat', 'DateFormat'); - $validator = new RequiredFields(); - $this->assertTrue($field->validate($validator)); - $field->setSubmittedValue([ - 'Options' => '__custom__', - 'Custom' => 'dd MM yyyy' - ]); - $this->assertTrue($field->validate($validator)); - $field->setSubmittedValue([ - 'Options' => '__custom__', - 'Custom' => 'sdfdsfdfd1244' - ]); - // @todo - Be less forgiving of invalid CLDR date format strings - $this->assertTrue($field->validate($validator)); - } - - public function testDescriptionTemplate() - { - $field = new MemberDatetimeOptionsetField('DateFormat', 'DateFormat'); - - $this->assertEmpty($field->getDescription()); - - $field->setDescription('Test description'); - $this->assertEquals('Test description', $field->getDescription()); - - $field->setDescriptionTemplate(get_class($field).'_description_time'); - $this->assertNotEmpty($field->getDescription()); - $this->assertNotEquals('Test description', $field->getDescription()); - } -} diff --git a/tests/php/Forms/MemberDatetimeOptionsetFieldTest.yml b/tests/php/Forms/MemberDatetimeOptionsetFieldTest.yml deleted file mode 100644 index 655d400df..000000000 --- a/tests/php/Forms/MemberDatetimeOptionsetFieldTest.yml +++ /dev/null @@ -1,6 +0,0 @@ -SilverStripe\Security\Member: - noformatmember: - Email: noformat@test.com - delocalemember: - Email: delocalemember@test.com - Locale: de_DE diff --git a/tests/php/Forms/TimeFieldTest.php b/tests/php/Forms/TimeFieldTest.php index 58d2e31dc..e2cf1e337 100644 --- a/tests/php/Forms/TimeFieldTest.php +++ b/tests/php/Forms/TimeFieldTest.php @@ -40,6 +40,17 @@ class TimeFieldTest extends SapphireTest $this->assertFalse($f->validate(new RequiredFields())); } + public function testValidateLenientWithHtml5() + { + $f = new TimeField('Time', 'Time', '23:59:59'); + $f->setHTML5(true); + $this->assertTrue($f->validate(new RequiredFields())); + + $f = new TimeField('Time', 'Time', '23:59'); // leave out seconds + $f->setHTML5(true); + $this->assertTrue($f->validate(new RequiredFields())); + } + public function testSetLocale() { // should get en_NZ by default through setUp() @@ -123,4 +134,24 @@ class TimeFieldTest extends SapphireTest $f->setValue('03:59:00'); $this->assertEquals($f->dataValue(), '03:59:00'); } + + public function testLenientSubmissionParseWithoutSecondsOnHtml5() + { + $f = new TimeField('Time', 'Time'); + $f->setHTML5(true); + $f->setSubmittedValue('23:59'); + $this->assertEquals($f->Value(), '23:59:00'); + } + + /** + * @expectedException \LogicException + */ + public function testHtml5WithCustomFormatThrowsException() + { + $f = new TimeField('Time', 'Time'); + $f->setValue('15:59:00'); + $f->setHTML5(true); + $f->setTimeFormat('mm:HH'); + $f->Value(); + } }