silverstripe-framework/forms/DatetimeField.php
Damian Mooyman 5c9044a007 API Enforce default_cast for all field usages
API Introduce HTMLFragment as casting helper for HTMLText with shortcodes disabled
API Introduce DBField::CDATA for XML file value encoding
API RSSFeed now casts from the underlying model rather than by override
API Introduce CustomMethods::getExtraMethodConfig() to allow metadata to be queried
BUG Remove _call hack from VirtualPage
API Remove FormField::$dontEscape
API Introduce HTMLReadonlyField for non-editable readonly HTML
API FormField::Field() now returns string in many cases rather than DBField instance.
API Remove redundant *_val methods from ViewableData
API ViewableData::obj() no longer has a $forceReturnObject parameter as it always returns an object
BUG  Fix issue with ViewableData caching incorrect field values after being modified.
API Remove deprecated DB class methods
API Enforce plain text left/right formfield titles
2016-07-13 17:15:45 +12:00

412 lines
12 KiB
PHP

<?php
use SilverStripe\ORM\FieldType\DBField;
/**
* A composite field for date and time entry,
* based on {@link DateField} and {@link TimeField}.
* Usually saves into a single {@link DBDateTime} database column.
* If you want to save into {@link Date} or {@link Time} columns,
* please instanciate the fields separately.
*
* # Configuration
*
* The {@link setConfig()} method is only used to configure common properties of this field.
* To configure the {@link DateField} and {@link TimeField} instances contained within, use their own
* {@link setConfig()} methods.
*
* Example:
* <code>
* $field = new DatetimeField('Name', 'Label');
* $field->setConfig('datavalueformat', 'yyyy-MM-dd HH:mm'); // global setting
* $field->getDateField()->setConfig('showcalendar', 1); // field-specific setting
* </code>
*
* - "timezone": Set a different timezone for viewing. {@link dataValue()} will still save
* the time in PHP's default timezone (date_default_timezone_get()), its only a view setting.
* Note that the sub-fields ({@link getDateField()} and {@link getTimeField()})
* are not timezone aware, and will have their values set in local time, rather than server time.
* - "datetimeorder": An sprintf() template to determine in which order the date and time values will
* be combined. This is necessary as those separate formats are set in their invididual fields.
*
* @package framework
* @subpackage forms
*/
class DatetimeField extends FormField {
/**
* @var DateField
*/
protected $dateField = null;
/**
* @var TimeField
*/
protected $timeField = null;
protected $schemaDataType = FormField::SCHEMA_DATA_TYPE_DATETIME;
/**
* @config
* @var array
*/
private static $default_config = array(
'datavalueformat' => 'yyyy-MM-dd HH:mm:ss',
'usertimezone' => null,
'datetimeorder' => '%s %s',
);
/**
* @var array
*/
protected $config;
public function __construct($name, $title = null, $value = ""){
$this->config = $this->config()->default_config;
$this->dateField = DateField::create($name . '[date]', false)
->addExtraClass('fieldgroup-field');
$this->timeField = TimeField::create($name . '[time]', false)
->addExtraClass('fieldgroup-field');
$this->timezoneField = new HiddenField($name . '[timezone]');
parent::__construct($name, $title, $value);
}
public function setForm($form) {
parent::setForm($form);
$this->dateField->setForm($form);
$this->timeField->setForm($form);
$this->timezoneField->setForm($form);
return $this;
}
public function setName($name) {
parent::setName($name);
$this->dateField->setName($name . '[date]');
$this->timeField->setName($name . '[time]');
$this->timezoneField->setName($name . '[timezone]');
return $this;
}
/**
* @param array $properties
* @return string
*/
public function FieldHolder($properties = array()) {
$config = array(
'datetimeorder' => $this->getConfig('datetimeorder'),
);
$config = array_filter($config);
$this->addExtraClass('fieldgroup');
$this->addExtraClass(Convert::raw2json($config));
return parent::FieldHolder($properties);
}
/**
* @param array $properties
* @return string
*/
public function Field($properties = array()) {
Requirements::css(FRAMEWORK_DIR . '/client/dist/styles/DatetimeField.css');
$tzField = ($this->getConfig('usertimezone')) ? $this->timezoneField->FieldHolder() : '';
return sprintf(
'%s%s%s<div class="clear"><!-- --></div>',
$this->dateField->FieldHolder(),
$this->timeField->FieldHolder(),
$tzField
);
}
/**
* Sets the internal value to ISO date format, based on either a database value in ISO date format,
* or a form submssion in the user date format. Uses the individual date and time fields
* to take care of the actual formatting and value conversion.
*
* Value setting happens *before* validation, so we have to set the value even if its not valid.
*
* Caution: Only converts user timezones when value is passed as array data (= form submission).
* Weak indication, but unfortunately the framework doesn't support a distinction between
* setting a value from the database, application logic, and user input.
*
* @param string|array $val String expects an ISO date format. Array notation with 'date' and 'time'
* keys can contain localized strings. If the 'dmyfields' option is used for {@link DateField},
* the 'date' value may contain array notation was well (see {@link DateField->setValue()}).
* @return $this
*/
public function setValue($val) {
$locale = new Zend_Locale($this->locale);
// If timezones are enabled, assume user data needs to be reverted to server timezone
if($this->getConfig('usertimezone')) {
// Accept user input on timezone, but only when timezone support is enabled
$userTz = (is_array($val) && array_key_exists('timezone', $val)) ? $val['timezone'] : null;
if(!$userTz) $userTz = $this->getConfig('usertimezone'); // fall back to defined timezone
} else {
$userTz = null;
}
if(empty($val)) {
$this->value = null;
$this->dateField->setValue(null);
$this->timeField->setValue(null);
} else {
// Case 1: String setting from database, in ISO date format
if(is_string($val) && Zend_Date::isDate($val, $this->getConfig('datavalueformat'), $locale)) {
$this->value = $val;
}
// Case 2: Array form submission with user date format
elseif(is_array($val) && array_key_exists('date', $val) && array_key_exists('time', $val)) {
$dataTz = date_default_timezone_get();
// If timezones are enabled, assume user data needs to be converted to server timezone
if($userTz) date_default_timezone_set($userTz);
// Uses sub-fields to temporarily write values and delegate dealing with their normalization,
// actual sub-field value setting happens later
$this->dateField->setValue($val['date']);
$this->timeField->setValue($val['time']);
if($this->dateField->dataValue() && $this->timeField->dataValue()) {
$userValueObj = new Zend_Date(null, null, $locale);
$userValueObj->setDate($this->dateField->dataValue(),
$this->dateField->getConfig('datavalueformat'));
$userValueObj->setTime($this->timeField->dataValue(),
$this->timeField->getConfig('datavalueformat'));
if($userTz) $userValueObj->setTimezone($dataTz);
$this->value = $userValueObj->get($this->getConfig('datavalueformat'), $locale);
unset($userValueObj);
} else {
// Validation happens later, so set the raw string in case Zend_Date doesn't accept it
$this->value = trim(sprintf($this->getConfig('datetimeorder'), $val['date'], $val['time']));
}
if($userTz) date_default_timezone_set($dataTz);
}
// Case 3: Value is invalid, but set it anyway to allow validation by the fields later on
else {
$this->dateField->setValue($val);
if(is_string($val) )$this->timeField->setValue($val);
$this->value = $val;
}
// view settings (dates might differ from $this->value based on user timezone settings)
if (Zend_Date::isDate($this->value, $this->getConfig('datavalueformat'), $locale)) {
$valueObj = new Zend_Date($this->value, $this->getConfig('datavalueformat'), $locale);
if($userTz) $valueObj->setTimezone($userTz);
// Set view values in sub-fields
if($this->dateField->getConfig('dmyfields')) {
$this->dateField->setValue($valueObj->toArray());
} else {
$this->dateField->setValue(
$valueObj->get($this->dateField->getConfig('dateformat'), $locale));
}
$this->timeField->setValue($valueObj->get($this->timeField->getConfig('timeformat'), $locale));
}
}
return $this;
}
public function Value() {
$valDate = $this->dateField->Value();
$valTime = $this->timeField->Value();
if(!$valTime) $valTime = '00:00:00';
return sprintf($this->getConfig('datetimeorder'), $valDate, $valTime);
}
public function setDisabled($bool) {
parent::setDisabled($bool);
$this->dateField->setDisabled($bool);
$this->timeField->setDisabled($bool);
if($this->timezoneField) $this->timezoneField->setDisabled($bool);
return $this;
}
public function setReadonly($bool) {
parent::setReadonly($bool);
$this->dateField->setReadonly($bool);
$this->timeField->setReadonly($bool);
if($this->timezoneField) $this->timezoneField->setReadonly($bool);
return $this;
}
/**
* @return DateField
*/
public function getDateField() {
return $this->dateField;
}
/**
* @param FormField
*/
public function setDateField($field) {
$expected = $this->getName() . '[date]';
if($field->getName() != $expected) {
throw new InvalidArgumentException(sprintf(
'Wrong name format for date field: "%s" (expected "%s")',
$field->getName(),
$expected
));
}
$field->setForm($this->getForm());
$this->dateField = $field;
$this->setValue($this->value); // update value
}
/**
* @return TimeField
*/
public function getTimeField() {
return $this->timeField;
}
/**
* @param FormField
*/
public function setTimeField($field) {
$expected = $this->getName() . '[time]';
if($field->getName() != $expected) {
throw new InvalidArgumentException(sprintf(
'Wrong name format for time field: "%s" (expected "%s")',
$field->getName(),
$expected
));
}
$field->setForm($this->getForm());
$this->timeField = $field;
$this->setValue($this->value); // update value
}
/**
* @return FormField
*/
public function getTimezoneField() {
return $this->timezoneField;
}
public function setLocale($locale) {
$this->dateField->setLocale($locale);
$this->timeField->setLocale($locale);
return $this;
}
public function getLocale() {
return $this->dateField->getLocale();
}
/**
* Note: Use {@link getDateField()} and {@link getTimeField()}
* to set field-specific config options.
*
* @param string $name
* @param mixed $val
*/
public function setConfig($name, $val) {
$this->config[$name] = $val;
if($name == 'usertimezone') {
$this->timezoneField->setValue($val);
$this->setValue($this->dataValue());
}
return $this;
}
/**
* Note: Use {@link getDateField()} and {@link getTimeField()}
* to get field-specific config options.
*
* @param String $name Optional, returns the whole configuration array if empty
* @return mixed
*/
public function getConfig($name = null) {
if($name) {
return isset($this->config[$name]) ? $this->config[$name] : null;
} else {
return $this->config;
}
}
public function validate($validator) {
$dateValid = $this->dateField->validate($validator);
$timeValid = $this->timeField->validate($validator);
return ($dateValid && $timeValid);
}
public function performReadonlyTransformation() {
$field = $this->castedCopy('DatetimeField_Readonly');
$field->setValue($this->dataValue());
$dateFieldConfig = $this->getDateField()->getConfig();
if($dateFieldConfig) {
foreach($dateFieldConfig as $k => $v) {
$field->getDateField()->setConfig($k, $v);
}
}
$timeFieldConfig = $this->getTimeField()->getConfig();
if($timeFieldConfig) {
foreach($timeFieldConfig as $k => $v) {
$field->getTimeField()->setConfig($k, $v);
}
}
return $field;
}
public function __clone() {
$this->dateField = clone $this->dateField;
$this->timeField = clone $this->timeField;
}
}
/**
* The readonly class for our {@link DatetimeField}.
*
* @package forms
* @subpackage fields-datetime
*/
class DatetimeField_Readonly extends DatetimeField {
protected $readonly = true;
public function Field($properties = array()) {
$valDate = $this->dateField->dataValue();
$valTime = $this->timeField->dataValue();
if($valDate && $valTime) {
$format = sprintf(
$this->getConfig('datetimeorder'),
$this->dateField->getConfig('dateformat'),
$this->timeField->getConfig('timeformat')
);
$valueObj = new Zend_Date(
sprintf($this->getConfig('datetimeorder'), $valDate, $valTime),
$this->getConfig('datavalueformat'),
$this->dateField->getLocale()
);
$val = $valueObj->toString($format);
} else {
$val = sprintf('<em>%s</em>', _t('DatetimeField.NOTSET', 'Not set'));
}
return "<span class=\"readonly\" id=\"" . $this->id() . "\">$val</span>";
}
}