From 0518364e2ab5dd19d13b14f84f521f370814096c Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Fri, 31 Mar 2017 10:37:21 +1300 Subject: [PATCH] API HTML5 DateField, remove member prefs (fixes #6626) --- src/Forms/DateField.php | 130 +++--------- src/Forms/DateField_View_JQuery.php | 197 ------------------ src/Security/Member.php | 107 +--------- tests/php/Forms/DatefieldViewJQueryTest.php | 47 ----- .../MemberDatetimeOptionsetFieldTest.php | 177 ---------------- .../MemberDatetimeOptionsetFieldTest.yml | 6 - 6 files changed, 38 insertions(+), 626 deletions(-) delete mode 100644 src/Forms/DateField_View_JQuery.php delete mode 100644 tests/php/Forms/DatefieldViewJQueryTest.php delete mode 100644 tests/php/Forms/MemberDatetimeOptionsetFieldTest.php delete mode 100644 tests/php/Forms/MemberDatetimeOptionsetFieldTest.yml diff --git a/src/Forms/DateField.php b/src/Forms/DateField.php index 4df37bb3f2c..832e19e05fc 100644 --- a/src/Forms/DateField.php +++ b/src/Forms/DateField.php @@ -115,25 +115,25 @@ class DateField extends TextField protected $rawValue = null; /** - * Check if calendar should be shown on the frontend - * + * @var bool + */ + protected $useHTML5 = false; + + /** * @return bool */ - public function getShowCalendar() + public function getUseHTML5() { - return $this->showCalendar; + return $this->useHTML5; } /** - * 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 setUseHTML5($bool) { - $this->showCalendar = $show; + $this->useHTML5 = $bool; return $this; } @@ -220,7 +220,10 @@ protected function getFormatter() ); // Don't invoke getDateFormat() directly to avoid infinite loop - if ($this->dateFormat) { + if ($this->getUseHTML5()) { + // Browsers expect ISO 8601 dates, localisation is handled on the client + $formatter->setPattern('y-MM-dd'); + } else if ($this->dateFormat) { $ok = $formatter->setPattern($this->dateFormat); if (!$ok) { throw new InvalidArgumentException("Invalid date format {$this->dateFormat}"); @@ -236,59 +239,39 @@ protected function getFormatter() */ 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); - }); - } + if ($this->getUseHTML5()) { + $this->setAttribute('type', 'date'); - /** - * 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(); + // 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->getUseHTML5()) { + $attributes['min'] = $this->getMinDate(); + $attributes['max'] = $this->getMaxDate(); } return $attributes; @@ -438,29 +421,6 @@ public function setLocale($locale) 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 +464,6 @@ public function setMaxDate($maxDate) 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 +530,4 @@ public function tidyISO8601($date) 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 b00539b8041..00000000000 --- 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/Security/Member.php b/src/Security/Member.php index e7090a706cc..1009e4a2492 100644 --- a/src/Security/Member.php +++ b/src/Security/Member.php @@ -19,7 +19,6 @@ 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 @@ public function splitName($name) } /** - * 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 @@ public function getLocale() } /** - * 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 @@ public function getCMSFields() 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 @@ public function getDefaultTimeFormat() 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 @@ public function fieldLabels($includerelations = true) $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/tests/php/Forms/DatefieldViewJQueryTest.php b/tests/php/Forms/DatefieldViewJQueryTest.php deleted file mode 100644 index 83b7a931a0f..00000000000 --- 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 5755b4fa8a2..00000000000 --- 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 655d400df54..00000000000 --- 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