Completed
Push — master ( c130e5...a809e8 )
by Sam
10:09
created

DatetimeField::setLocale()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 1
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * A composite field for date and time entry,
5
 * based on {@link DateField} and {@link TimeField}.
6
 * Usually saves into a single {@link DBDateTime} database column.
7
 * If you want to save into {@link Date} or {@link Time} columns,
8
 * please instanciate the fields separately.
9
 *
10
 * # Configuration
11
 *
12
 * The {@link setConfig()} method is only used to configure common properties of this field.
13
 * To configure the {@link DateField} and {@link TimeField} instances contained within, use their own
14
 * {@link setConfig()} methods.
15
 *
16
 * Example:
17
 * <code>
18
 * $field = new DatetimeField('Name', 'Label');
19
 * $field->setConfig('datavalueformat', 'yyyy-MM-dd HH:mm'); // global setting
20
 * $field->getDateField()->setConfig('showcalendar', 1); // field-specific setting
21
 * </code>
22
 *
23
 * - "timezone": Set a different timezone for viewing. {@link dataValue()} will still save
24
 * the time in PHP's default timezone (date_default_timezone_get()), its only a view setting.
25
 * Note that the sub-fields ({@link getDateField()} and {@link getTimeField()})
26
 * are not timezone aware, and will have their values set in local time, rather than server time.
27
 * - "datetimeorder": An sprintf() template to determine in which order the date and time values will
28
 * be combined. This is necessary as those separate formats are set in their invididual fields.
29
 *
30
 * @package framework
31
 * @subpackage forms
32
 */
33
class DatetimeField extends FormField {
34
35
	/**
36
	 * @var DateField
37
	 */
38
	protected $dateField = null;
39
40
	/**
41
	 * @var TimeField
42
	 */
43
	protected $timeField = null;
44
45
	/**
46
	 * @var HiddenField
47
	 */
48
	protected $timezoneField = null;
49
50
	protected $schemaDataType = FormField::SCHEMA_DATA_TYPE_DATETIME;
51
52
	/**
53
	 * @config
54
	 * @var array
55
	 */
56
	private static $default_config = array(
57
		'datavalueformat' => 'yyyy-MM-dd HH:mm:ss',
58
		'usertimezone' => null,
59
		'datetimeorder' => '%s %s',
60
	);
61
62
	/**
63
	 * @var array
64
	 */
65
	protected $config;
66
67
	public function __construct($name, $title = null, $value = ""){
68
		$this->config = $this->config()->default_config;
0 ignored issues
show
Documentation introduced by
The property default_config does not exist on object<Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
69
70
		$this->timeField = TimeField::create($name . '[time]', false);
71
		$this->dateField = DateField::create($name . '[date]', false);
72
		$this->timezoneField = new HiddenField($name . '[timezone]');
73
74
		parent::__construct($name, $title, $value);
75
	}
76
77
	public function setForm($form) {
78
		parent::setForm($form);
79
80
		$this->dateField->setForm($form);
81
		$this->timeField->setForm($form);
82
		$this->timezoneField->setForm($form);
83
84
		return $this;
85
	}
86
87
	public function setName($name) {
88
		parent::setName($name);
89
90
		$this->dateField->setName($name . '[date]');
91
		$this->timeField->setName($name . '[time]');
92
		$this->timezoneField->setName($name . '[timezone]');
93
94
		return $this;
95
	}
96
97
	/**
98
	 * @param array $properties
99
	 * @return string
100
	 */
101
	public function FieldHolder($properties = array()) {
102
		$config = array(
103
			'datetimeorder' => $this->getConfig('datetimeorder'),
104
		);
105
		$config = array_filter($config);
106
		$this->addExtraClass(Convert::raw2json($config));
107
108
		return parent::FieldHolder($properties);
109
	}
110
111
	/**
112
	 * @param array $properties
113
	 * @return string
114
	 */
115
	public function Field($properties = array()) {
116
		Requirements::css(FRAMEWORK_DIR . '/client/dist/styles/DatetimeField.css');
117
		return parent::Field($properties);
118
	}
119
120
	/**
121
	 * Sets the internal value to ISO date format, based on either a database value in ISO date format,
122
	 * or a form submssion in the user date format. Uses the individual date and time fields
123
	 * to take care of the actual formatting and value conversion.
124
	 *
125
	 * Value setting happens *before* validation, so we have to set the value even if its not valid.
126
	 *
127
	 * Caution: Only converts user timezones when value is passed as array data (= form submission).
128
	 * Weak indication, but unfortunately the framework doesn't support a distinction between
129
	 * setting a value from the database, application logic, and user input.
130
	 *
131
	 * @param string|array $val String expects an ISO date format. Array notation with 'date' and 'time'
132
	 *  keys can contain localized strings. If the 'dmyfields' option is used for {@link DateField},
133
	 *  the 'date' value may contain array notation was well (see {@link DateField->setValue()}).
134
	 * @return $this
135
	 */
136
	public function setValue($val) {
137
		$locale = new Zend_Locale($this->locale);
138
139
		// If timezones are enabled, assume user data needs to be reverted to server timezone
140
		if($this->getConfig('usertimezone')) {
141
			// Accept user input on timezone, but only when timezone support is enabled
142
			$userTz = (is_array($val) && array_key_exists('timezone', $val)) ? $val['timezone'] : null;
143
			if(!$userTz) $userTz = $this->getConfig('usertimezone'); // fall back to defined timezone
144
		} else {
145
			$userTz = null;
146
		}
147
148
		if(empty($val)) {
149
			$this->value = null;
150
			$this->dateField->setValue(null);
151
			$this->timeField->setValue(null);
152
		} else {
153
			// Case 1: String setting from database, in ISO date format
154
			if(is_string($val) && Zend_Date::isDate($val, $this->getConfig('datavalueformat'), $locale)) {
155
				$this->value = $val;
156
			}
157
			// Case 2: Array form submission with user date format
158
			elseif(is_array($val) && array_key_exists('date', $val) && array_key_exists('time', $val)) {
159
160
				$dataTz = date_default_timezone_get();
161
				// If timezones are enabled, assume user data needs to be converted to server timezone
162
				if($userTz) date_default_timezone_set($userTz);
163
164
				// Uses sub-fields to temporarily write values and delegate dealing with their normalization,
165
				// actual sub-field value setting happens later
166
				$this->dateField->setValue($val['date']);
167
				$this->timeField->setValue($val['time']);
168
				if($this->dateField->dataValue() && $this->timeField->dataValue()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->dateField->dataValue() of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
Bug Best Practice introduced by
The expression $this->timeField->dataValue() of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
169
					$userValueObj = new Zend_Date(null, null, $locale);
170
					$userValueObj->setDate($this->dateField->dataValue(),
171
						$this->dateField->getConfig('datavalueformat'));
172
					$userValueObj->setTime($this->timeField->dataValue(),
173
						$this->timeField->getConfig('datavalueformat'));
174
					if($userTz) $userValueObj->setTimezone($dataTz);
175
					$this->value = $userValueObj->get($this->getConfig('datavalueformat'), $locale);
176
					unset($userValueObj);
177
				} else {
178
					// Validation happens later, so set the raw string in case Zend_Date doesn't accept it
179
					$this->value = trim(sprintf($this->getConfig('datetimeorder'), $val['date'], $val['time']));
180
				}
181
182
				if($userTz) date_default_timezone_set($dataTz);
183
			}
184
			// Case 3: Value is invalid, but set it anyway to allow validation by the fields later on
185
			else {
186
				$this->dateField->setValue($val);
187
				if(is_string($val) )$this->timeField->setValue($val);
188
				$this->value = $val;
189
			}
190
191
			// view settings (dates might differ from $this->value based on user timezone settings)
192
			if (Zend_Date::isDate($this->value, $this->getConfig('datavalueformat'), $locale)) {
193
				$valueObj = new Zend_Date($this->value, $this->getConfig('datavalueformat'), $locale);
194
				if($userTz) $valueObj->setTimezone($userTz);
195
196
				// Set view values in sub-fields
197
				if($this->dateField->getConfig('dmyfields')) {
198
					$this->dateField->setValue($valueObj->toArray());
199
				} else {
200
					$this->dateField->setValue(
201
						$valueObj->get($this->dateField->getConfig('dateformat'), $locale));
202
				}
203
				$this->timeField->setValue($valueObj->get($this->timeField->getConfig('timeformat'), $locale));
204
			}
205
		}
206
207
		return $this;
208
	}
209
210
	public function Value() {
211
		$valDate = $this->dateField->Value();
212
		$valTime = $this->timeField->Value();
213
		if(!$valTime) $valTime = '00:00:00';
214
215
		return sprintf($this->getConfig('datetimeorder'), $valDate, $valTime);
216
	}
217
218
	public function setDisabled($bool) {
219
		parent::setDisabled($bool);
220
		$this->dateField->setDisabled($bool);
221
		$this->timeField->setDisabled($bool);
222
		if($this->timezoneField) $this->timezoneField->setDisabled($bool);
223
		return $this;
224
	}
225
226
	public function setReadonly($bool) {
227
		parent::setReadonly($bool);
228
		$this->dateField->setReadonly($bool);
229
		$this->timeField->setReadonly($bool);
230
		if($this->timezoneField) $this->timezoneField->setReadonly($bool);
231
		return $this;
232
	}
233
234
	/**
235
	 * @return DateField
236
	 */
237
	public function getDateField() {
238
		return $this->dateField;
239
	}
240
241
	/**
242
	 * @param FormField
243
	 */
244
	public function setDateField($field) {
245
		$expected = $this->getName() . '[date]';
246
		if($field->getName() != $expected) {
247
			throw new InvalidArgumentException(sprintf(
248
				'Wrong name format for date field: "%s" (expected "%s")',
249
				$field->getName(),
250
				$expected
251
			));
252
		}
253
254
		$field->setForm($this->getForm());
255
		$this->dateField = $field;
256
		$this->setValue($this->value); // update value
257
	}
258
259
	/**
260
	 * @return TimeField
261
	 */
262
	public function getTimeField() {
263
		return $this->timeField;
264
	}
265
266
	/**
267
	 * @param FormField
268
	 */
269
	public function setTimeField($field) {
270
		$expected = $this->getName() . '[time]';
271
		if($field->getName() != $expected) {
272
			throw new InvalidArgumentException(sprintf(
273
				'Wrong name format for time field: "%s" (expected "%s")',
274
				$field->getName(),
275
				$expected
276
			));
277
		}
278
279
		$field->setForm($this->getForm());
280
		$this->timeField = $field;
281
		$this->setValue($this->value); // update value
282
	}
283
284
	/**
285
	 * Check if timezone field is included
286
	 *
287
	 * @return bool
288
	 */
289
	public function getHasTimezone() {
290
		return $this->getConfig('usertimezone');
291
	}
292
293
	/**
294
	 * @return FormField
295
	 */
296
	public function getTimezoneField() {
297
		return $this->timezoneField;
298
	}
299
300
	public function setLocale($locale) {
301
		$this->dateField->setLocale($locale);
302
		$this->timeField->setLocale($locale);
303
		return $this;
304
	}
305
306
	public function getLocale() {
307
		return $this->dateField->getLocale();
308
	}
309
310
	/**
311
	 * Note: Use {@link getDateField()} and {@link getTimeField()}
312
	 * to set field-specific config options.
313
	 *
314
	 * @param string $name
315
	 * @param mixed $val
316
	 */
317
	public function setConfig($name, $val) {
318
		$this->config[$name] = $val;
319
320
		if($name == 'usertimezone') {
321
			$this->timezoneField->setValue($val);
322
			$this->setValue($this->dataValue());
323
		}
324
325
		return $this;
326
	}
327
328
	/**
329
	 * Note: Use {@link getDateField()} and {@link getTimeField()}
330
	 * to get field-specific config options.
331
	 *
332
	 * @param String $name Optional, returns the whole configuration array if empty
333
	 * @return mixed
334
	 */
335
	public function getConfig($name = null) {
336
		if($name) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $name of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
337
			return isset($this->config[$name]) ? $this->config[$name] : null;
338
		} else {
339
			return $this->config;
340
		}
341
	}
342
343
	public function validate($validator) {
344
		$dateValid = $this->dateField->validate($validator);
345
		$timeValid = $this->timeField->validate($validator);
346
347
		return ($dateValid && $timeValid);
348
	}
349
350
	public function performReadonlyTransformation() {
351
		$field = clone $this;
352
		$field->setReadonly(true);
353
		return $field;
354
	}
355
356
	public function __clone() {
357
		$this->dateField = clone $this->dateField;
358
		$this->timeField = clone $this->timeField;
359
		$this->timezoneField = clone $this->timezoneField;
360
	}
361
}
362