diff --git a/admin/client/dist/styles/bundle.css b/admin/client/dist/styles/bundle.css
index b836322e7..d323dbf7e 100644
--- a/admin/client/dist/styles/bundle.css
+++ b/admin/client/dist/styles/bundle.css
@@ -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;
}
diff --git a/admin/client/src/styles/legacy/_forms.scss b/admin/client/src/styles/legacy/_forms.scss
index 36fb6508d..dbcb5038c 100644
--- a/admin/client/src/styles/legacy/_forms.scss
+++ b/admin/client/src/styles/legacy/_forms.scss
@@ -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
diff --git a/docs/en/02_Developer_Guides/03_Forms/01_Validation.md b/docs/en/02_Developer_Guides/03_Forms/01_Validation.md
index 61422cd3d..a757f42be 100644
--- a/docs/en/02_Developer_Guides/03_Forms/01_Validation.md
+++ b/docs/en/02_Developer_Guides/03_Forms/01_Validation.md
@@ -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.
-
-
-The data value of the `FormField` submitted is not passed into validate. It is stored in the `value` property through
-the `setValue` method.
-
+`Validator` instance.
```php
public function validate($validator)
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 d02519aca..71946f46f 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
@@ -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');
-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).
@@ -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');
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.
-
-
-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.
-
-
## 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());
Fields scaffolded through [api:DataObject::scaffoldCMSFields()] automatically have a description attached to them.
diff --git a/docs/en/04_Changelogs/4.0.0.md b/docs/en/04_Changelogs/4.0.0.md
index 1938f200c..f7a341e6e 100644
--- a/docs/en/04_Changelogs/4.0.0.md
+++ b/docs/en/04_Changelogs/4.0.0.md
@@ -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)
#### 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`
#### 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.
### 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()`
#### i18n API Removed API
diff --git a/src/Forms/DateField.php b/src/Forms/DateField.php
index 8089a5360..6a24bad34 100644
--- a/src/Forms/DateField.php
+++ b/src/Forms/DateField.php
@@ -8,9 +8,7 @@ use InvalidArgumentException;
use SilverStripe\ORM\FieldType\DBDatetime;
/**
- * Form field to display an editable date string,
- * either in a single `` 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 = ' / ';
- 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);
- }
}
diff --git a/src/Forms/HTMLEditor/TinyMCEConfig.php b/src/Forms/HTMLEditor/TinyMCEConfig.php
index 3fc83c33c..e51b68d6d 100644
--- a/src/Forms/HTMLEditor/TinyMCEConfig.php
+++ b/src/Forms/HTMLEditor/TinyMCEConfig.php
@@ -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;
diff --git a/src/Forms/MemberDatetimeOptionsetField.php b/src/Forms/MemberDatetimeOptionsetField.php
index 8e0913da7..e49b91e1b 100644
--- a/src/Forms/MemberDatetimeOptionsetField.php
+++ b/src/Forms/MemberDatetimeOptionsetField.php
@@ -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);
}
diff --git a/src/Forms/MoneyField.php b/src/Forms/MoneyField.php
index 58fe123ad..cfda10542 100644
--- a/src/Forms/MoneyField.php
+++ b/src/Forms/MoneyField.php
@@ -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;
}
diff --git a/src/Forms/NumericField.php b/src/Forms/NumericField.php
index 122b149ee..63a2007fb 100644
--- a/src/Forms/NumericField.php
+++ b/src/Forms/NumericField.php
@@ -306,4 +306,11 @@ class NumericField extends TextField
$this->scale = $scale;
return $this;
}
+
+ public function performReadonlyTransformation()
+ {
+ $field = clone $this;
+ $field->setReadonly(true);
+ return $field;
+ }
}
diff --git a/src/Forms/NumericField_Readonly.php b/src/Forms/NumericField_Readonly.php
deleted file mode 100644
index 52a80f64c..000000000
--- a/src/Forms/NumericField_Readonly.php
+++ /dev/null
@@ -1,30 +0,0 @@
-value ?: '0';
- }
-
- public function getValueCast()
- {
- return 'Decimal';
- }
-}
diff --git a/src/Forms/SeparatedDateField.php b/src/Forms/SeparatedDateField.php
new file mode 100644
index 000000000..dc6999a95
--- /dev/null
+++ b/src/Forms/SeparatedDateField.php
@@ -0,0 +1,136 @@
+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 = ' / ';
+ 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);
+ }
+}
diff --git a/src/ORM/FieldType/DBDate.php b/src/ORM/FieldType/DBDate.php
index 461530acc..1e1fd26e8 100644
--- a/src/ORM/FieldType/DBDate.php
+++ b/src/ORM/FieldType/DBDate.php
@@ -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('#^(?\\d{4})/(?\\d+)/(?\\d+)(?