PageRenderTime 57ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 1ms

/forms/DatetimeField.php

http://github.com/silverstripe/sapphire
PHP | 361 lines | 196 code | 50 blank | 115 comment | 34 complexity | 455f08ca9d746f10ec3b35636af33d66 MD5 | raw file
Possible License(s): BSD-3-Clause, MIT, CC-BY-3.0, GPL-2.0, AGPL-1.0, LGPL-2.1
  1. <?php
  2. /**
  3. * A composite field for date and time entry,
  4. * based on {@link DateField} and {@link TimeField}.
  5. * Usually saves into a single {@link DBDateTime} database column.
  6. * If you want to save into {@link Date} or {@link Time} columns,
  7. * please instanciate the fields separately.
  8. *
  9. * # Configuration
  10. *
  11. * The {@link setConfig()} method is only used to configure common properties of this field.
  12. * To configure the {@link DateField} and {@link TimeField} instances contained within, use their own
  13. * {@link setConfig()} methods.
  14. *
  15. * Example:
  16. * <code>
  17. * $field = new DatetimeField('Name', 'Label');
  18. * $field->setConfig('datavalueformat', 'yyyy-MM-dd HH:mm'); // global setting
  19. * $field->getDateField()->setConfig('showcalendar', 1); // field-specific setting
  20. * </code>
  21. *
  22. * - "timezone": Set a different timezone for viewing. {@link dataValue()} will still save
  23. * the time in PHP's default timezone (date_default_timezone_get()), its only a view setting.
  24. * Note that the sub-fields ({@link getDateField()} and {@link getTimeField()})
  25. * are not timezone aware, and will have their values set in local time, rather than server time.
  26. * - "datetimeorder": An sprintf() template to determine in which order the date and time values will
  27. * be combined. This is necessary as those separate formats are set in their invididual fields.
  28. *
  29. * @package framework
  30. * @subpackage forms
  31. */
  32. class DatetimeField extends FormField {
  33. /**
  34. * @var DateField
  35. */
  36. protected $dateField = null;
  37. /**
  38. * @var TimeField
  39. */
  40. protected $timeField = null;
  41. /**
  42. * @var HiddenField
  43. */
  44. protected $timezoneField = null;
  45. protected $schemaDataType = FormField::SCHEMA_DATA_TYPE_DATETIME;
  46. /**
  47. * @config
  48. * @var array
  49. */
  50. private static $default_config = array(
  51. 'datavalueformat' => 'yyyy-MM-dd HH:mm:ss',
  52. 'usertimezone' => null,
  53. 'datetimeorder' => '%s %s',
  54. );
  55. /**
  56. * @var array
  57. */
  58. protected $config;
  59. public function __construct($name, $title = null, $value = ""){
  60. $this->config = $this->config()->default_config;
  61. $this->timeField = TimeField::create($name . '[time]', false);
  62. $this->dateField = DateField::create($name . '[date]', false);
  63. $this->timezoneField = new HiddenField($name . '[timezone]');
  64. parent::__construct($name, $title, $value);
  65. }
  66. public function setForm($form) {
  67. parent::setForm($form);
  68. $this->dateField->setForm($form);
  69. $this->timeField->setForm($form);
  70. $this->timezoneField->setForm($form);
  71. return $this;
  72. }
  73. public function setName($name) {
  74. parent::setName($name);
  75. $this->dateField->setName($name . '[date]');
  76. $this->timeField->setName($name . '[time]');
  77. $this->timezoneField->setName($name . '[timezone]');
  78. return $this;
  79. }
  80. /**
  81. * @param array $properties
  82. * @return string
  83. */
  84. public function FieldHolder($properties = array()) {
  85. $config = array(
  86. 'datetimeorder' => $this->getConfig('datetimeorder'),
  87. );
  88. $config = array_filter($config);
  89. $this->addExtraClass(Convert::raw2json($config));
  90. return parent::FieldHolder($properties);
  91. }
  92. /**
  93. * @param array $properties
  94. * @return string
  95. */
  96. public function Field($properties = array()) {
  97. Requirements::css(FRAMEWORK_DIR . '/client/dist/styles/DatetimeField.css');
  98. return parent::Field($properties);
  99. }
  100. /**
  101. * Sets the internal value to ISO date format, based on either a database value in ISO date format,
  102. * or a form submssion in the user date format. Uses the individual date and time fields
  103. * to take care of the actual formatting and value conversion.
  104. *
  105. * Value setting happens *before* validation, so we have to set the value even if its not valid.
  106. *
  107. * Caution: Only converts user timezones when value is passed as array data (= form submission).
  108. * Weak indication, but unfortunately the framework doesn't support a distinction between
  109. * setting a value from the database, application logic, and user input.
  110. *
  111. * @param string|array $val String expects an ISO date format. Array notation with 'date' and 'time'
  112. * keys can contain localized strings. If the 'dmyfields' option is used for {@link DateField},
  113. * the 'date' value may contain array notation was well (see {@link DateField->setValue()}).
  114. * @return $this
  115. */
  116. public function setValue($val) {
  117. $locale = new Zend_Locale($this->locale);
  118. // If timezones are enabled, assume user data needs to be reverted to server timezone
  119. if($this->getConfig('usertimezone')) {
  120. // Accept user input on timezone, but only when timezone support is enabled
  121. $userTz = (is_array($val) && array_key_exists('timezone', $val)) ? $val['timezone'] : null;
  122. if(!$userTz) $userTz = $this->getConfig('usertimezone'); // fall back to defined timezone
  123. } else {
  124. $userTz = null;
  125. }
  126. if(empty($val)) {
  127. $this->value = null;
  128. $this->dateField->setValue(null);
  129. $this->timeField->setValue(null);
  130. } else {
  131. // Case 1: String setting from database, in ISO date format
  132. if(is_string($val) && Zend_Date::isDate($val, $this->getConfig('datavalueformat'), $locale)) {
  133. $this->value = $val;
  134. }
  135. // Case 2: Array form submission with user date format
  136. elseif(is_array($val) && array_key_exists('date', $val) && array_key_exists('time', $val)) {
  137. $dataTz = date_default_timezone_get();
  138. // If timezones are enabled, assume user data needs to be converted to server timezone
  139. if($userTz) date_default_timezone_set($userTz);
  140. // Uses sub-fields to temporarily write values and delegate dealing with their normalization,
  141. // actual sub-field value setting happens later
  142. $this->dateField->setValue($val['date']);
  143. $this->timeField->setValue($val['time']);
  144. if($this->dateField->dataValue() && $this->timeField->dataValue()) {
  145. $userValueObj = new Zend_Date(null, null, $locale);
  146. $userValueObj->setDate($this->dateField->dataValue(),
  147. $this->dateField->getConfig('datavalueformat'));
  148. $userValueObj->setTime($this->timeField->dataValue(),
  149. $this->timeField->getConfig('datavalueformat'));
  150. if($userTz) $userValueObj->setTimezone($dataTz);
  151. $this->value = $userValueObj->get($this->getConfig('datavalueformat'), $locale);
  152. unset($userValueObj);
  153. } else {
  154. // Validation happens later, so set the raw string in case Zend_Date doesn't accept it
  155. $this->value = trim(sprintf($this->getConfig('datetimeorder'), $val['date'], $val['time']));
  156. }
  157. if($userTz) date_default_timezone_set($dataTz);
  158. }
  159. // Case 3: Value is invalid, but set it anyway to allow validation by the fields later on
  160. else {
  161. $this->dateField->setValue($val);
  162. if(is_string($val) )$this->timeField->setValue($val);
  163. $this->value = $val;
  164. }
  165. // view settings (dates might differ from $this->value based on user timezone settings)
  166. if (Zend_Date::isDate($this->value, $this->getConfig('datavalueformat'), $locale)) {
  167. $valueObj = new Zend_Date($this->value, $this->getConfig('datavalueformat'), $locale);
  168. if($userTz) $valueObj->setTimezone($userTz);
  169. // Set view values in sub-fields
  170. if($this->dateField->getConfig('dmyfields')) {
  171. $this->dateField->setValue($valueObj->toArray());
  172. } else {
  173. $this->dateField->setValue(
  174. $valueObj->get($this->dateField->getConfig('dateformat'), $locale));
  175. }
  176. $this->timeField->setValue($valueObj->get($this->timeField->getConfig('timeformat'), $locale));
  177. }
  178. }
  179. return $this;
  180. }
  181. public function Value() {
  182. $valDate = $this->dateField->Value();
  183. $valTime = $this->timeField->Value();
  184. if(!$valTime) $valTime = '00:00:00';
  185. return sprintf($this->getConfig('datetimeorder'), $valDate, $valTime);
  186. }
  187. public function setDisabled($bool) {
  188. parent::setDisabled($bool);
  189. $this->dateField->setDisabled($bool);
  190. $this->timeField->setDisabled($bool);
  191. if($this->timezoneField) $this->timezoneField->setDisabled($bool);
  192. return $this;
  193. }
  194. public function setReadonly($bool) {
  195. parent::setReadonly($bool);
  196. $this->dateField->setReadonly($bool);
  197. $this->timeField->setReadonly($bool);
  198. if($this->timezoneField) $this->timezoneField->setReadonly($bool);
  199. return $this;
  200. }
  201. /**
  202. * @return DateField
  203. */
  204. public function getDateField() {
  205. return $this->dateField;
  206. }
  207. /**
  208. * @param FormField
  209. */
  210. public function setDateField($field) {
  211. $expected = $this->getName() . '[date]';
  212. if($field->getName() != $expected) {
  213. throw new InvalidArgumentException(sprintf(
  214. 'Wrong name format for date field: "%s" (expected "%s")',
  215. $field->getName(),
  216. $expected
  217. ));
  218. }
  219. $field->setForm($this->getForm());
  220. $this->dateField = $field;
  221. $this->setValue($this->value); // update value
  222. }
  223. /**
  224. * @return TimeField
  225. */
  226. public function getTimeField() {
  227. return $this->timeField;
  228. }
  229. /**
  230. * @param FormField
  231. */
  232. public function setTimeField($field) {
  233. $expected = $this->getName() . '[time]';
  234. if($field->getName() != $expected) {
  235. throw new InvalidArgumentException(sprintf(
  236. 'Wrong name format for time field: "%s" (expected "%s")',
  237. $field->getName(),
  238. $expected
  239. ));
  240. }
  241. $field->setForm($this->getForm());
  242. $this->timeField = $field;
  243. $this->setValue($this->value); // update value
  244. }
  245. /**
  246. * Check if timezone field is included
  247. *
  248. * @return bool
  249. */
  250. public function getHasTimezone() {
  251. return $this->getConfig('usertimezone');
  252. }
  253. /**
  254. * @return FormField
  255. */
  256. public function getTimezoneField() {
  257. return $this->timezoneField;
  258. }
  259. public function setLocale($locale) {
  260. $this->dateField->setLocale($locale);
  261. $this->timeField->setLocale($locale);
  262. return $this;
  263. }
  264. public function getLocale() {
  265. return $this->dateField->getLocale();
  266. }
  267. /**
  268. * Note: Use {@link getDateField()} and {@link getTimeField()}
  269. * to set field-specific config options.
  270. *
  271. * @param string $name
  272. * @param mixed $val
  273. */
  274. public function setConfig($name, $val) {
  275. $this->config[$name] = $val;
  276. if($name == 'usertimezone') {
  277. $this->timezoneField->setValue($val);
  278. $this->setValue($this->dataValue());
  279. }
  280. return $this;
  281. }
  282. /**
  283. * Note: Use {@link getDateField()} and {@link getTimeField()}
  284. * to get field-specific config options.
  285. *
  286. * @param String $name Optional, returns the whole configuration array if empty
  287. * @return mixed
  288. */
  289. public function getConfig($name = null) {
  290. if($name) {
  291. return isset($this->config[$name]) ? $this->config[$name] : null;
  292. } else {
  293. return $this->config;
  294. }
  295. }
  296. public function validate($validator) {
  297. $dateValid = $this->dateField->validate($validator);
  298. $timeValid = $this->timeField->validate($validator);
  299. return ($dateValid && $timeValid);
  300. }
  301. public function performReadonlyTransformation() {
  302. $field = clone $this;
  303. $field->setReadonly(true);
  304. return $field;
  305. }
  306. public function __clone() {
  307. $this->dateField = clone $this->dateField;
  308. $this->timeField = clone $this->timeField;
  309. $this->timezoneField = clone $this->timezoneField;
  310. }
  311. }