API Create SeparatedDateField

API Restrict allowed values parsed via DBDate::setValue
API Remove NumericField_Readonly
API Remove DBTime::Nice12 / Nice24
This commit is contained in:
Damian Mooyman 2017-02-14 18:19:09 +13:00
parent 029a8b9586
commit 014f0d23ed
No known key found for this signature in database
GPG Key ID: 78B823A10DE27D1A
20 changed files with 274 additions and 446 deletions

View File

@ -8859,11 +8859,16 @@ fieldset{
height:18px;
}
.field input.day,.field input.month,.field input.year{
.field input.day,.field input.month{
width:56px;
display:inline;
}
.field input.year{
width:72px;
display:inline;
}
.field input.time{
width:88px;
}

View File

@ -295,10 +295,14 @@ form.small .field, .field.small {
}
/* Date Fields */
input.month, input.day, input.year {
input.month, input.day {
width: ($grid-x * 7);
display: inline;
}
input.year {
width: ($grid-x * 9);
display: inline;
}
input.time {
width: ($grid-x * 11); // smaller time field, since input is restricted

View File

@ -66,12 +66,7 @@ Each individual [api:FormField] instance is responsible for validating the submi
Subclasses of `FormField` can define their own version of `validate` to provide custom validation rules such as the
above example with the `Email` validation. The `validate` method on `FormField` takes a single argument of the current
`Validator` instance.
<div class="notice" markdown="1">
The data value of the `FormField` submitted is not passed into validate. It is stored in the `value` property through
the `setValue` method.
</div>
`Validator` instance.
```php
public function validate($validator)

View File

@ -3,8 +3,8 @@ summary: How to format and use the DateField class.
# DateField
This `FormField` subclass lets you display an editable date, either in a single text input field, or in three separate
fields for day, month and year. It also provides a calendar date picker.
This `FormField` subclass lets you display an editable date, in a single text input field.
It also provides a calendar date picker.
The following example will add a simple DateField to your Page, allowing you to enter a date manually.
@ -33,14 +33,14 @@ The following example will add a simple DateField to your Page, allowing you to
## Custom Date Format
A custom date format for a [api:DateField] can be provided through `setConfig`.
A custom date format for a [api:DateField] can be provided through `setDateFormat`.
:::php
// will display a date in the following format: 31-06-2012
DateField::create('MyDate')->setDateFormat('dd-MM-yyyy');
<div class="info" markdown="1">
The formats are based on [Zend_Date constants](http://framework.zend.com/manual/1.12/en/zend.date.constants.html).
The formats are based on [CLDR format](http://userguide.icu-project.org/formatparse/datetime).
</div>
@ -51,19 +51,16 @@ strtotime()).
:::php
DateField::create('MyDate')
->setConfig('min', '-7 days')
->setConfig('max', '2012-12-31')
->setMinDate('-7 days')
->setMaxDate'2012-12-31')
## Separate Day / Month / Year Fields
The following setting will display your DateField as three input fields for day, month and year separately. HTML5
placeholders 'day', 'month' and 'year' are enabled by default.
To display separate input fields for day, month and year separately you can use the `DateFieldSeparated` subclass`.
HTML5 placeholders 'day', 'month' and 'year' are enabled by default.
:::php
DateField::create('MyDate')
->setConfig('dmyfields', true)
->setConfig('dmyseparator', '/') // set the separator
->setConfig('dmyplaceholders', 'true'); // enable HTML 5 Placeholders
DateFieldSeparated::create('MyDate');
<div class="alert" markdown="1">
Any custom date format settings will be ignored.
@ -75,10 +72,13 @@ The following setting will add a Calendar to a single DateField, using the jQuer
:::php
DateField::create('MyDate')
->setConfig('showcalendar', true);
->setShowCalendar(true);
The jQuery DatePicker doesn't support every constant available for `Zend_Date`. If you choose to use the calendar, the
following constants should at least be safe:
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.
It is recommended to use numeric format, as `MMM` or `MMMM` month names may not always pass validation.
Constant | xxxxx
-------- | -----
@ -94,15 +94,6 @@ y | year (4 digits)
yy | year (2 digits)
yyyy | year (4 digits)
Unfortunately the day- and monthname values in Zend Date do not always match those in the existing jQuery UI locale
files, so constants like `EEE` or `MMM`, for day and month names could break validation. To fix this we had to slightly
alter the jQuery locale files, situated in */framework/thirdparty/jquery-ui/datepicker/i18n/*, to match Zend_Date.
<div class="info">
At this moment not all locale files may be present. If a locale file is missing, the DatePicker calendar will fallback
to 'yyyy-MM-dd' whenever day - and/or monthnames are used. After saving, the correct format will be displayed.
</div>
## Formatting Hints
It's often not immediate apparent which format a field accepts, and showing the technical format (e.g. `HH:mm:ss`) is
@ -113,13 +104,14 @@ field description as an example.
$dateField = DateField::create('MyDate');
// Show long format as text below the field
$dateField->setDescription(sprintf(
_t('FormField.Example', 'e.g. %s', 'Example format'),
Convert::raw2xml(Zend_Date::now()->toString($dateField->getConfig('dateformat')))
$dateField->setDescription(_t(
'FormField.Example',
'e.g. {format}',
[ 'format' => $dateField->getDateFormat() ]
));
// Alternatively, set short format as a placeholder in the field
$dateField->setAttribute('placeholder', $dateField->getConfig('dateformat'));
$dateField->setAttribute('placeholder', $dateField->getDateFormat());
<div class="notice" markdown="1">
Fields scaffolded through [api:DataObject::scaffoldCMSFields()] automatically have a description attached to them.

View File

@ -942,6 +942,9 @@ specific functions.
by the array key, or the `class` parameter value.
* Uniqueness checks for `File.Name` is performed on write only (not in `setName()`)
* Created `Resettable` interface to better declare objects which should be reset between tests.
* Added a server requirement for the php-intl extension (shipped by default with most PHP distributions)
* Replaced Zend_Date and Zend_Locale with the php-intl extension.
* Consistently use CLDR date formats (rather than a mix of CLDR and date() formats)
#### <a name="overview-general-removed"></a>General and Core Removed API
@ -1052,17 +1055,23 @@ A very small number of methods were chosen for deprecation, and will be removed
* `ChangeSet` and `ChangeSetItem` have been added for batch publishing of versioned dataobjects.
* `DataObject.table_name` config can now be used to customise the database table for any record.
* `DataObjectSchema` class added to assist with mapping between classes and tables.
* `DBMoney` values are now treated as empty only Amount is null. Values without Currency
will be formatted in the default locale.
* `DBMoney` values are now treated as empty only if `Amount` field is null. If an `Amount` value
is provided without a `Currency` specified, it will be formatted as per the current locale.
The below methods have been added or had their functionality updated to `DBDate`, `DBTime` and `DBDatetime`
* `getTimestamp()` added to get the respective date / time as unix timestamp (seconds since 1970-01-01)
* `Format()` method now use CLDR format strings, rather than PHP format strings.
See http://userguide.icu-project.org/formatparse/datetime.
* `Format()` method now use [CLDR format strings](http://userguide.icu-project.org/formatparse/datetime),
rather than [PHP format string](http://php.net/manual/en/function.date.php).
E.g. `d/m/Y H:i:s` (php format) should be replaced with to `dd/MM/y HH:mm:ss` (CLDR format).
* getISOFormat() added which returns the standard date/time ISO 8601 pattern in CLDR format.
* Dates passed in m/d/y format will now raise a notice but will be parsed.
Dates passed to constructors should follow ISO 8601 (y-m-d).
* 2-digit years will raise a notice.
* `setValue` method is now a lot more restrictive, and expects dates and times to be passed in
ISO 8601 format (y-MM-dd) or (HH:mm:ss). Certain date formats will attempt to parse with
the below restrictions:
- `/`, `.` or `-` are supported date separators, but will be replaced with `-` internally.
- US date formats (m-d-y / y-d-m) will not be supported and may be parsed incorrectly.
(Note: Date form fields will still support localised date formats).
- `dd-MM-y` will be converted to `y-MM-dd` internally.
- 2-digit values for year will now raise errors.
* `FormatFromSettings` will default to `Nice()` format if no member is logged in.
* `Nice`, `Long` and `Full` methods will now follow standard formatting rules for the
current locale, rather than pre-defined formats.
@ -1071,7 +1080,6 @@ The below methods have been added or had their functionality updated to `DBDate`
`DBTime` specific changes:
* Added `DBTime::FormatFromSettings`
* Added `DBTime::Nice12`
#### <a name="overview-orm-removed"></a>ORM Removed API
@ -1115,7 +1123,9 @@ The below methods have been added or had their functionality updated to `DBDate`
- `days_between`
* `nice_format` has been removed from `DBDate` / `DBTime` / `DBDatetime` has been removed in favour of
locale-specific formatting for Nice()
* Removed `DBTime::TwelveHour`
* Removed several `DBTime` methods:
- `TwelveHour`
- `Nice24`
* Removed some `DBMoney` methods due to lack of support in php-intl.
- `NiceWithShortname`
- `NiceWithName`
@ -1235,6 +1245,9 @@ The following filesystem synchronisation methods and tasks are also removed
* Introduced `AssetAdmin\Forms\UploadField` as a react-friendly version of UploadField. This may also
be used in normal entwine forms for managing files in a similar way to UploadField. However, this
does not support inline editing of files.
* Added method `FormField::setSubmittedValue($value, $data)` to process input submitted from form
submission, in contrast to `FormField::setValue($value, $data)` which is intended to load its
value from the ORM. The second argument to setValue() has been added.
The following methods and properties on `Requirements_Backend` have been renamed:
@ -1320,6 +1333,7 @@ New `DatetimeField` methods replace `getConfig()` / `setConfig()`:
* `getTimezone()` / `setTimezone()`
* `getDateTimeOrder()` / `setDateTimeOrder()`
* `getLocale()` / `setLocale()`
* `datavaluefield` config is removed as internal data value is now fixed to ISO 8601 format
New `DateField` methods replace `getConfig()` / `setConfig()`:
@ -1328,9 +1342,9 @@ New `DateField` methods replace `getConfig()` / `setConfig()`:
* `getMinDate()` / `setMinDate()`
* `getMaxDate()` / `setMaxDate()`
* `getPlaceholders()` / `setPlaceholders()`
* `getSeparateDMYFields()` / `setSeparateDMYFields()`
* `getClientLocale` / `setClientLocale`
* `getLocale()` / `setLocale()`
* option `dmyfields` is now superceded with an `SeparatedDateField` class
New `TimeField` methods replace `getConfig()` / `setConfig()`
@ -1370,6 +1384,7 @@ New `TimeField` methods replace `getConfig()` / `setConfig()`
as they are obsolete.
* Removed `DatetimeField`, `DateField` and `TimeField` methods `getConfig` and `setConfig`. Individual
getters and setters for individual options are provided instead. See above for list of new methods.
* Removed `NumericField_Readonly`. Use `setReadonly(true)` instead.
### <a name="overview-i18n"></a>i18n API
@ -1383,20 +1398,20 @@ New `TimeField` methods replace `getConfig()` / `setConfig()`
for all DataObject subclasses, rather than just the basename without namespace.
* i18n key for locale-respective pluralisation rules added as '.PLURALS'. These can be configured
within yaml in array format as per [ruby i18n pluralization rules](http://guides.rubyonrails.org/i18n.html#pluralization).
* `i18n.all_locales` config moved to `Locales.locales`
* `i18n.common_languages` config moved to `Locales.languages`
* `i18n.likely_subtags` config moved to `Locales.likely_subtags`
* `i18n.tinymce_lang` config moved to `TinyMCEConfig.tinymce_lang`
* `i18n::get_tinymce_lang()` moved to `TinyMCEConfig::get_tinymce_lang()`
* `i18n::get_locale_from_lang()` moved to `Locales::localeFromLang()`
* `i18n::get_lange_from_locale()` moved to `Locales::langFromLocale()`
* `i18n::validate_locale()` moved to `Locales::validate()`
* `i18n::get_common_languages()` moved to `Locales::getLanguages()`
* `i18n::get_locale_name()` moved to `Locales::localeName()`
* `i18n::get_language_name()` moved to `Locales::languageName()`
* `i18n.module_priority` config moved to `Sources.module_priority`
* `i18n::get_owner_module()` moved to `ClassManifest::getOwnerModule()`
* `i18n::get_existing_translations()` moved to `Sources::getKnownLocales()`
* `i18n.all_locales` config moved to `SilverStripe\i18n\Data\Locales.locales`
* `i18n.common_languages` config moved to `SilverStripe\i18n\Data\Locales.languages`
* `i18n.likely_subtags` config moved to `SilverStripe\i18n\Data\Locales.likely_subtags`
* `i18n.tinymce_lang` config moved to `SilverStripe\Forms\HTMLEditor\TinyMCEConfig.tinymce_lang`
* `i18n::get_tinymce_lang()` moved to `SilverStripe\Forms\HTMLEditor\TinyMCEConfig::get_tinymce_lang()`
* `i18n::get_locale_from_lang()` moved to `SilverStripe\i18n\Data\Locales::localeFromLang()`
* `i18n::get_lange_from_locale()` moved to `SilverStripe\i18n\Data\Locales::langFromLocale()`
* `i18n::validate_locale()` moved to `SilverStripe\i18n\Data\Locales::validate()`
* `i18n::get_common_languages()` moved to `SilverStripe\i18n\Data\Locales::getLanguages()`
* `i18n::get_locale_name()` moved to `SilverStripe\i18n\Data\Locales::localeName()`
* `i18n::get_language_name()` moved to `SilverStripe\i18n\Data\Locales::languageName()`
* `i18n.module_priority` config moved to `SilverStripe\i18n\Data\Sources.module_priority`
* `i18n::get_owner_module()` moved to `SilverStripe\Core\Manifest\ClassManifest::getOwnerModule()`
* `i18n::get_existing_translations()` moved to `SilverStripe\i18n\Data\Sources::getKnownLocales()`
#### <a name="overview-i18n-removed"></a>i18n API Removed API

View File

@ -8,9 +8,7 @@ use InvalidArgumentException;
use SilverStripe\ORM\FieldType\DBDatetime;
/**
* Form field to display an editable date string,
* either in a single `<input type="text">` field,
* or in three separate fields for day, month and year.
* Form used for editing a date stirng
*
* Caution: The form field does not include any JavaScript or CSS when used outside of the CMS context,
* since the required frontend dependencies are included through CMS bundling.
@ -29,11 +27,10 @@ use SilverStripe\ORM\FieldType\DBDatetime;
*
* # Usage
*
* ## Example: German dates with separate fields for day, month, year
* ## Example: Field localised with german date format
*
* $f = new DateField('MyDate');
* $f->setLocale('de_DE');
* $f->setSeparateDMYFields(true);
*
* # Validation
*
@ -95,14 +92,6 @@ class DateField extends TextField
*/
protected $placeholders = true;
/**
* Declare whether D, M and Y fields should be separate inputs.
* If set then only numeric values will be accepted.
*
* @var bool
*/
protected $separateDMYFields = false;
/**
* Override locale for client side.
*
@ -145,16 +134,11 @@ class DateField extends TextField
/**
* Set if calendar should be shown on the frontend.
*
* If set to true, disables separate DMY fields
*
* @param bool $show
* @return $this
*/
public function setShowCalendar($show)
{
if ($show && $this->getSeparateDMYFields()) {
throw new InvalidArgumentException("Can't separate DMY fields and show calendar popup");
}
$this->showCalendar = $show;
return $this;
}
@ -316,39 +300,6 @@ class DateField extends TextField
return $attributes;
}
public function Field($properties = array())
{
if (!$this->getSeparateDMYFields()) {
return parent::Field($properties);
}
// Three separate fields for day, month and year
$valArr = $this->iso8601ToArray($this->Value());
$fieldDay = NumericField::create($this->name . '[day]', false, $valArr ? $valArr['day'] : null)
->addExtraClass('day')
->setMaxLength(2);
$fieldMonth = NumericField::create($this->name . '[month]', false, $valArr ? $valArr['month'] : null)
->addExtraClass('month')
->setMaxLength(2);
$fieldYear = NumericField::create($this->name . '[year]', false, $valArr ? $valArr['year'] : null)
->addExtraClass('year')
->setMaxLength(4);
// Set placeholders
if ($this->getPlaceholders()) {
$fieldDay->setAttribute('placeholder', _t(__CLASS__ . '.DAY', 'Day'));
$fieldMonth->setAttribute('placeholder', _t(__CLASS__ . '.MONTH', 'Month'));
$fieldYear->setAttribute('placeholder', _t(__CLASS__ . '.YEAR', 'Year'));
}
// Join all fields
// @todo custom ordering based on locale
$sep = '&nbsp;<span class="separator">/</span>&nbsp;';
return $fieldDay->Field() . $sep
. $fieldMonth->Field() . $sep
. $fieldYear->Field();
}
public function Type()
{
return 'date text';
@ -364,11 +315,7 @@ class DateField extends TextField
public function setSubmittedValue($value, $data = null)
{
// Save raw value for later validation
if ($this->isEmptyArray($value)) {
$this->rawValue = null;
} else {
$this->rawValue = $value;
}
$this->rawValue = $value;
// Null case
if (!$value) {
@ -376,12 +323,6 @@ class DateField extends TextField
return $this;
}
// If loading from array convert
if (is_array($value)) {
$this->value = $this->arrayToISO8601($value);
return $this;
}
// Parse from submitted value
$this->value = $this->localisedToISO8601($value);
return $this;
@ -555,33 +496,6 @@ class DateField extends TextField
return $this;
}
/**
* Declare whether D, M and Y fields should be separate inputs.
* If set then only numeric values will be accepted.
*
* @return bool
*/
public function getSeparateDMYFields()
{
return $this->separateDMYFields;
}
/**
* Set if we should separate D M and Y fields. If set to true, disabled calendar
* popup.
*
* @param bool $separate
* @return $this
*/
public function setSeparateDMYFields($separate)
{
if ($separate && $this->getShowCalendar()) {
throw new InvalidArgumentException("Can't separate DMY fields and show calendar popup");
}
$this->separateDMYFields = $separate;
return $this;
}
/**
* @return string
*/
@ -647,62 +561,6 @@ class DateField extends TextField
return $config;
}
/**
* Convert iso 8601 date to array (day / month / year)
*
* @param string $date
* @return array|null Array form, or null if not valid
*/
public function iso8601ToArray($date)
{
if (!$date) {
return null;
}
$formatter = $this->getISO8601Formatter();
$timestamp = $formatter->parse($date);
if ($timestamp === false) {
return null;
}
// Format time manually into an array
return [
'day' => date('j', $timestamp),
'month' => date('n', $timestamp),
'year' => date('Y', $timestamp),
];
}
/**
* Convert array to timestamp
*
* @param array $value
* @return string
*/
public function arrayToISO8601($value)
{
if ($this->isEmptyArray($value)) {
return null;
}
// ensure all keys are specified
if (!isset($value['month']) || !isset($value['day']) || !isset($value['year'])) {
return null;
}
// Ensure valid range
if (!checkdate($value['month'], $value['day'], $value['year'])) {
return null;
}
// Note: Set formatter to strict for array input
$formatter = $this->getISO8601Formatter();
$timestamp = mktime(0, 0, 0, $value['month'], $value['day'], $value['year']);
if ($timestamp === false) {
return null;
}
return $formatter->format($timestamp);
}
/**
* Convert date localised in the current locale to ISO 8601 date
*
@ -776,15 +634,4 @@ class DateField extends TextField
{
return DateField_View_JQuery::create($this);
}
/**
* Check if this array is empty
*
* @param $value
* @return bool
*/
public function isEmptyArray($value)
{
return is_array($value) && !array_filter($value);
}
}

View File

@ -2,7 +2,6 @@
namespace SilverStripe\Forms\HTMLEditor;
use SilverStripe\Core\Config\Configurable;
use SilverStripe\Core\Convert;
use SilverStripe\Control\Controller;
use SilverStripe\Control\Director;

View File

@ -159,7 +159,7 @@ class MemberDatetimeOptionsetField extends OptionsetField
public function setValue($value, $data = null)
{
if (is_array($value)) {
throw new InvalidArgumentException("Invalid value");
throw new InvalidArgumentException("Invalid array value: Expected string");
}
return parent::setValue($value, $data);
}

View File

@ -89,7 +89,7 @@ class MoneyField extends FormField
$currencyValue = $this->fieldCurrency ? $this->fieldCurrency->dataValue() : null;
$allowedCurrencies = $this->getAllowedCurrencies();
if (count($allowedCurrencies) === 1) {
// Dropdown field for multiple currencies
// Hidden field for single currency
$field = HiddenField::create("{$name}[Currency]");
reset($allowedCurrencies);
$currencyValue = key($allowedCurrencies);
@ -164,7 +164,7 @@ class MoneyField extends FormField
'Currency' => $value->getCurrency(),
'Amount' => $value->getAmount(),
];
} else {
} elseif (!is_array($value)) {
throw new InvalidArgumentException("Invalid currency format");
}
@ -232,8 +232,6 @@ class MoneyField extends FormField
public function performReadonlyTransformation()
{
$clone = clone $this;
$clone->fieldAmount = $clone->fieldAmount->performReadonlyTransformation();
$clone->fieldCurrency = $clone->fieldCurrency->performReadonlyTransformation();
$clone->setReadonly(true);
return $clone;
}

View File

@ -306,4 +306,11 @@ class NumericField extends TextField
$this->scale = $scale;
return $this;
}
public function performReadonlyTransformation()
{
$field = clone $this;
$field->setReadonly(true);
return $field;
}
}

View File

@ -1,30 +0,0 @@
<?php
namespace SilverStripe\Forms;
/**
* Readonly version of a numeric field.
*/
class NumericField_Readonly extends ReadonlyField
{
/**
* @return static
*/
public function performReadonlyTransformation()
{
return clone $this;
}
/**
* @return string
*/
public function Value()
{
return $this->value ?: '0';
}
public function getValueCast()
{
return 'Decimal';
}
}

View File

@ -0,0 +1,136 @@
<?php
namespace SilverStripe\Forms;
use SilverStripe\i18n\i18n;
/**
* Date field with separate inputs for d/m/y
*/
class SeparatedDateField extends DateField
{
public function Field($properties = array())
{
// Three separate fields for day, month and year
$valArr = $this->iso8601ToArray($this->dataValue());
$fieldDay = NumericField::create($this->name . '[day]', false, $valArr ? $valArr['day'] : null)
->addExtraClass('day')
->setHTML5(true)
->setMaxLength(2);
$fieldMonth = NumericField::create($this->name . '[month]', false, $valArr ? $valArr['month'] : null)
->addExtraClass('month')
->setHTML5(true)
->setMaxLength(2);
$fieldYear = NumericField::create($this->name . '[year]', false, $valArr ? $valArr['year'] : null)
->addExtraClass('year')
->setHTML5(true)
->setMaxLength(4);
// Set placeholders
if ($this->getPlaceholders()) {
$fieldDay->setAttribute('placeholder', _t(__CLASS__ . '.DAY', 'Day'));
$fieldMonth->setAttribute('placeholder', _t(__CLASS__ . '.MONTH', 'Month'));
$fieldYear->setAttribute('placeholder', _t(__CLASS__ . '.YEAR', 'Year'));
}
// Join all fields
// @todo custom ordering based on locale
$sep = '&nbsp;<span class="separator">/</span>&nbsp;';
return $fieldDay->Field() . $sep
. $fieldMonth->Field() . $sep
. $fieldYear->Field();
}
/**
* Convert array to timestamp
*
* @param array $value
* @return string
*/
public function arrayToISO8601($value)
{
if ($this->isEmptyArray($value)) {
return null;
}
// ensure all keys are specified
if (!isset($value['month']) || !isset($value['day']) || !isset($value['year'])) {
return null;
}
// Ensure valid range
if (!checkdate($value['month'], $value['day'], $value['year'])) {
return null;
}
// Note: Set formatter to strict for array input
$formatter = $this->getISO8601Formatter();
$timestamp = mktime(0, 0, 0, $value['month'], $value['day'], $value['year']);
if ($timestamp === false) {
return null;
}
return $formatter->format($timestamp);
}
/**
* Convert iso 8601 date to array (day / month / year)
*
* @param string $date
* @return array|null Array form, or null if not valid
*/
public function iso8601ToArray($date)
{
if (!$date) {
return null;
}
$formatter = $this->getISO8601Formatter();
$timestamp = $formatter->parse($date);
if ($timestamp === false) {
return null;
}
// Format time manually into an array
return [
'day' => date('j', $timestamp),
'month' => date('n', $timestamp),
'year' => date('Y', $timestamp),
];
}
/**
* Assign value posted from form submission
*
* @param mixed $value
* @param mixed $data
* @return $this
*/
public function setSubmittedValue($value, $data = null)
{
// Filter out empty arrays
if ($this->isEmptyArray($value)) {
$value = null;
}
$this->rawValue = $value;
// Null case
if (!$value || !is_array($value)) {
$this->value = null;
return $this;
}
// Parse
$this->value = $this->arrayToISO8601($value);
return $this;
}
/**
* Check if this array is empty
*
* @param $value
* @return bool
*/
public function isEmptyArray($value)
{
return is_array($value) && !array_filter($value);
}
}

View File

@ -38,7 +38,7 @@ class DBDate extends DBField
$value = $this->parseDate($value);
if ($value === false) {
throw new InvalidArgumentException(
"Invalid date passed. Use " . self::ISO_DATE . " to prevent this error."
"Invalid date: '$value'. Use " . self::ISO_DATE . " to prevent this error."
);
}
$this->value = $value;
@ -308,10 +308,7 @@ class DBDate extends DBField
*/
public function Rfc2822()
{
if ($this->value) {
return date('Y-m-d H:i:s', $this->getTimestamp());
}
return null;
return $this->Format('y-MM-dd HH:mm:ss');
}
/**
@ -321,15 +318,13 @@ class DBDate extends DBField
*/
public function Rfc3339()
{
if (!$this->value) {
$date = $this->Format('y-MM-dd\\THH:mm:ss');
if (!$date) {
return null;
}
$timestamp = $this->getTimestamp();
$date = date('Y-m-d\TH:i:s', $timestamp);
$matches = array();
if (preg_match('/^([\-+])(\d{2})(\d{2})$/', date('O', $timestamp), $matches)) {
if (preg_match('/^([\-+])(\d{2})(\d{2})$/', date('O', $this->getTimestamp()), $matches)) {
$date .= $matches[1].$matches[2].':'.$matches[3];
} else {
$date .= 'Z';
@ -541,23 +536,13 @@ class DBDate extends DBField
protected function fixInputDate($value)
{
// split
list($day, $month, $year, $time) = $this->explodeDateString($value);
// Detect invalid year order
if (!checkdate($month, $day, $year) && checkdate($month, $year, $day)) {
trigger_error(
"Unexpected date order. Use " . self::ISO_DATE . " to prevent this notice.",
E_USER_NOTICE
);
list($day, $year) = [$year, $day];
}
// Fix y2k year
$year = $this->guessY2kYear($year);
list($year, $month, $day, $time) = $this->explodeDateString($value);
// Validate date
if (!checkdate($month, $day, $year)) {
throw new InvalidArgumentException("Invalid date passed. Use " . self::ISO_DATE . " to prevent this error.");
throw new InvalidArgumentException(
"Invalid date: '$value'. Use " . self::ISO_DATE . " to prevent this error."
);
}
// Convert to y-m-d
@ -565,65 +550,39 @@ class DBDate extends DBField
}
/**
* Attempt to split date string into day, month, year, and timestamp components.
* Don't read this code without a drink in hand!
* Attempt to split date string into year, month, day, and timestamp components.
*
* @param string $value
* @return array
*/
protected function explodeDateString($value)
{
// US date format with 4-digit year first
if (preg_match('#^(?<year>\\d{4})/(?<day>\\d+)/(?<month>\\d+)(?<time>.*)$#', $value, $matches)) {
trigger_error(
"Implicit y/d/m conversion. Use " . self::ISO_DATE . " to prevent this notice.",
E_USER_NOTICE
// split on known delimiters (. / -)
if (!preg_match(
'#^(?<first>\\d+)[-/\\.](?<second>\\d+)[-/\\.](?<third>\\d+)(?<time>.*)$#',
$value,
$matches
)) {
throw new InvalidArgumentException(
"Invalid date: '$value'. Use " . self::ISO_DATE . " to prevent this error."
);
return [$matches['day'], $matches['month'], $matches['year'], $matches['time']];
}
// US date format without 4-digit year first: assume m/d/y
if (preg_match('#^(?<month>\\d+)/(?<day>\\d+)/(?<year>\\d+)(?<time>.*)$#', $value, $matches)) {
// Assume m/d/y
trigger_error(
"Implicit m/d/y conversion. Use " . self::ISO_DATE . " to prevent this notice.",
E_USER_NOTICE
$parts = [
$matches['first'],
$matches['second'],
$matches['third']
];
// Flip d-m-y to y-m-d
if ($parts[0] < 1000 && $parts[2] > 1000) {
$parts = array_reverse($parts);
}
if ($parts[0] < 1000) {
throw new InvalidArgumentException(
"Invalid date: '$value'. Use " . self::ISO_DATE . " to prevent this error."
);
return [$matches['day'], $matches['month'], $matches['year'], $matches['time']];
}
// check d.m.y
if (preg_match('#^(?<day>\\d+)\\.(?<month>\\d+)\\.(?<year>\\d+)(?<time>.*)$#', $value, $matches)) {
return [$matches['day'], $matches['month'], $matches['year'], $matches['time']];
}
// check y-m-d
if (preg_match('#^(?<year>\\d+)\\-(?<month>\\d+)\\-(?<day>\\d+)(?<time>.*)$#', $value, $matches)) {
return [$matches['day'], $matches['month'], $matches['year'], $matches['time']];
}
throw new InvalidArgumentException(
"Invalid date passed. Use " . self::ISO_DATE . " to prevent this error."
);
}
/**
* @param int $year
* @return int Fixed year
*/
protected function guessY2kYear($year)
{
// Fix y2k
if ($year < 100) {
trigger_error("Implicit y2k conversion. Please use full YYYY year for dates", E_USER_NOTICE);
if ($year >= 70) {
// 70 -> 99 converted to 19(x)
$year += 1900;
} else {
// 0 -> 69 converted to 20(x)
$year += 2000;
}
}
return $year;
$parts[] = $matches['time'];
return $parts;
}
}

View File

@ -110,26 +110,6 @@ class DBTime extends DBField
return $formatter->format($this->getTimestamp());
}
/**
* Returns the time in 12-hour format using the format string 'h:mm a' e.g. '1:32 pm'.
*
* @return string Formatted time.
*/
public function Nice12()
{
return $this->Format('h:mm a');
}
/**
* Returns the time in 24-hour format using the format string 'H:mm' e.g. '13:32'.
*
* @return string Formatted time.
*/
public function Nice24()
{
return $this->Format('H:mm');
}
/**
* Return the time using a particular formatting string.
*

View File

@ -1606,8 +1606,8 @@ class IntlLocales implements Locales, Resettable
return false;
}
return strcasecmp($lang, $region)
* strcasecmp($lang, $locale)
* strcasecmp($region, $locale) !== 0;
&& strcasecmp($lang, $locale)
&& strcasecmp($region, $locale);
}
/**

View File

@ -4,6 +4,7 @@ namespace SilverStripe\Forms\Tests;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Forms\DateField;
use SilverStripe\Forms\SeparatedDateField;
use SilverStripe\Forms\RequiredFields;
use SilverStripe\i18n\i18n;
use SilverStripe\ORM\FieldType\DBDatetime;
@ -110,8 +111,7 @@ class DateFieldTest extends SapphireTest
public function testSetValueWithDateArray()
{
$f = new DateField('Date', 'Date');
$f->setSeparateDMYFields(true);
$f = new SeparatedDateField('Date', 'Date');
$f->setSubmittedValue([
'day' => 29,
'month' => 03,
@ -147,10 +147,8 @@ class DateFieldTest extends SapphireTest
public function testEmptyValueValidation()
{
$field = new DateField('Date');
$validator = new RequiredFields();
$this->assertTrue($field->validate($validator));
$field->setSeparateDMYFields(true);
$field = new SeparatedDateField('Date');
$this->assertTrue($field->validate($validator));
$field->setSubmittedValue([
'day' => '',
@ -162,8 +160,7 @@ class DateFieldTest extends SapphireTest
public function testValidateArray()
{
$f = new DateField('Date', 'Date');
$f->setSeparateDMYFields(true);
$f = new SeparatedDateField('Date', 'Date');
$f->setSubmittedValue([
'day' => 29,
'month' => 03,
@ -193,9 +190,7 @@ class DateFieldTest extends SapphireTest
public function testValidateEmptyArrayValuesSetsNullForValueObject()
{
$f = new DateField('Date', 'Date');
$f->setSeparateDMYFields(true);
$f = new SeparatedDateField('Date', 'Date');
$f->setSubmittedValue([
'day' => '',
'month' => '',
@ -213,7 +208,7 @@ class DateFieldTest extends SapphireTest
public function testValidateArrayValue()
{
$f = new DateField('Date', 'Date');
$f = new SeparatedDateField('Date', 'Date');
$f->setSubmittedValue(['day' => 29, 'month' => 03, 'year' => 2003]);
$this->assertTrue($f->validate(new RequiredFields()));

View File

@ -7,6 +7,7 @@ use SilverStripe\Control\Controller;
use SilverStripe\Forms\DatetimeField;
use SilverStripe\Forms\RequiredFields;
use SilverStripe\Forms\DateField;
use SilverStripe\Forms\SeparatedDateField;
use SilverStripe\Forms\Tests\DatetimeFieldTest\Model;
use SilverStripe\Forms\TimeField;
use SilverStripe\Forms\FieldList;
@ -107,7 +108,7 @@ class DatetimeFieldTest extends SapphireTest
public function testSetValueWithDmyArray()
{
$f = new DatetimeField('Datetime', 'Datetime');
$f->getDateField()->setSeparateDMYFields(true);
$f->setDateField(new SeparatedDateField('Datetime[date]'));
$f->setSubmittedValue([
'date' => ['day' => 29, 'month' => 03, 'year' => 2003],
'time' => '11:00:00 pm'

View File

@ -107,9 +107,13 @@ class NumericFieldTest extends SapphireTest
public function testReadonly()
{
i18n::set_locale('en_US');
$field = new NumericField('Number');
$this->assertRegExp("#<span[^>]+>\s*0\s*<\/span>#", "".$field->performReadonlyTransformation()->Field()."");
$field->setLocale('de_DE');
$field->setScale(2);
$field->setValue(1001.3);
$html = $field->performReadonlyTransformation()->Field()->forTemplate();
$this->assertContains('value="1.001,30"', $html);
$this->assertContains('readonly="readonly"', $html);
}
public function testNumberTypeOnInputHtml()

View File

@ -94,96 +94,30 @@ class DBDateTest extends SapphireTest
public function testMDYConversion()
{
// Disable notices
$this->suppressNotices();
$this->assertEquals(
'4/03/2003',
DBField::create_field('Date', '3/4/2003')->Nice(),
"Date->Nice() works with M/D/YYYY format"
);
$this->setExpectedException(
PHPUnit_Framework_Error_Notice::class,
"Implicit m/d/y conversion. Use " . DBDate::ISO_DATE . " to prevent this notice."
\InvalidArgumentException::class,
"Invalid date: '3/16/2003'. Use " . DBDate::ISO_DATE . " to prevent this error."
);
$this->restoreNotices();
DBField::create_field('Date', '3/4/2003');
}
public function testYDMConversion()
{
// Disable notices
$this->suppressNotices();
$this->assertEquals(
'4/03/2003',
DBField::create_field('Date', '2003/4/3')->Nice(),
"Date->Nice() works with YYYY/D/M format"
);
$this->setExpectedException(
PHPUnit_Framework_Error_Notice::class,
"Implicit y/d/m conversion. Use " . DBDate::ISO_DATE . " to prevent this notice."
);
$this->restoreNotices();
DBField::create_field('Date', '2003/4/3');
DBField::create_field('Date', '3/16/2003');
}
public function testY2kCorrection()
{
$this->suppressNotices();
$this->assertEquals(
'4/03/2003',
DBField::create_field('Date', '4.3.03')->Nice(),
"Date->Nice() works with D.M.YY format"
);
$this->assertEquals(
'4/03/2003',
DBField::create_field('Date', '04.03.03')->Nice(),
"Date->Nice() works with DD.MM.YY format"
);
$this->assertEquals(
'4/03/2003',
DBField::create_field('Date', '4.3.03')->Nice(),
"Date->Nice() works with D.M.YY format"
);
$this->assertEquals(
'4/03/2003',
DBField::create_field('Date', '4.03.03')->Nice(),
"Date->Nice() works with D.M.YY format"
);
$this->assertEquals(
'4/03/2003',
DBField::create_field('Date', '03-03-04')->Nice(),
"Date->Nice() works with Y-m-d format"
);
$this->setExpectedException(
PHPUnit_Framework_Error_Notice::class,
"Implicit y2k conversion. Please use full YYYY year for dates"
\InvalidArgumentException::class,
"Invalid date: '03-03-04'. Use " . DBDate::ISO_DATE . " to prevent this error."
);
$this->restoreNotices();
DBField::create_field('Date', '03-03-04');
}
public function testInvertedYearCorrection()
{
$this->suppressNotices();
// iso8601 expects year first
// iso8601 expects year first, but support year last
$this->assertEquals(
'4/03/2003',
DBField::create_field('Date', '04-03-2003')->Nice(),
"Date->Nice() works with DD-MM-YYYY format"
);
$this->setExpectedException(
PHPUnit_Framework_Error_Notice::class,
"Unexpected date order. Use " . DBDate::ISO_DATE . " to prevent this notice."
);
$this->restoreNotices();
DBField::create_field('Date', '04-03-2003');
}
public function testYear()
@ -400,7 +334,6 @@ class DBDateTest extends SapphireTest
return [
['2000-12-31', '31/12/2000'],
['31-12-2000', '31/12/2000'],
['12/31/2000', '31/12/2000'],
['2014-04-01', '01/04/2014'],
];
}

View File

@ -57,18 +57,6 @@ class DBTimeTest extends SapphireTest
$this->assertEquals('5:15 PM', $time->Short());
}
public function testNice12()
{
$time = DBTime::create_field('Time', '17:15:55');
$this->assertEquals('5:15 PM', $time->Nice12());
}
public function testNice24()
{
$time = DBTime::create_field('Time', '17:15:55');
$this->assertEquals('17:15', $time->Nice24());
}
public function dataTestFormatFromSettings()
{
return [