Completed
Push — correct-classname-values ( f9b487 )
by Sam
08:29
created

DatetimeField_Readonly   A

Complexity

Total Complexity 3

Size/Duplication

Total Lines 29
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
dl 0
loc 29
rs 10
c 0
b 0
f 0
wmc 3
lcom 1
cbo 3
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 View Code Duplication
	public function setForm($form) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 View Code Duplication
	public function FieldHolder($properties = array()) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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()) {
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 View Code Duplication
	public function setDisabled($bool) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 View Code Duplication
	public function setReadonly($bool) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 View Code Duplication
	public function setDateField($field) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 View Code Duplication
	public function setTimeField($field) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 View Code Duplication
	public function getConfig($name = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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