Subversion-Projekte lars-tiefland.cakephp

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
/* SVN FILE: $Id: form.php 8004 2009-01-16 20:15:21Z gwoo $ */
3
/**
4
 * Automatic generation of HTML FORMs from given data.
5
 *
6
 * Used for scaffolding.
7
 *
8
 * PHP versions 4 and 5
9
 *
10
 * CakePHP(tm) :  Rapid Development Framework (http://www.cakephp.org)
11
 * Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
12
 *
13
 * Licensed under The MIT License
14
 * Redistributions of files must retain the above copyright notice.
15
 *
16
 * @filesource
17
 * @copyright     Copyright 2005-2008, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
18
 * @link          http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
19
 * @package       cake
20
 * @subpackage    cake.cake.libs.view.helpers
21
 * @since         CakePHP(tm) v 0.10.0.1076
22
 * @version       $Revision: 8004 $
23
 * @modifiedby    $LastChangedBy: gwoo $
24
 * @lastmodified  $Date: 2009-01-16 12:15:21 -0800 (Fri, 16 Jan 2009) $
25
 * @license       http://www.opensource.org/licenses/mit-license.php The MIT License
26
 */
27
/**
28
 * Form helper library.
29
 *
30
 * Automatic generation of HTML FORMs from given data.
31
 *
32
 * @package       cake
33
 * @subpackage    cake.cake.libs.view.helpers
34
 */
35
class FormHelper extends AppHelper {
36
/**
37
 * Other helpers used by FormHelper
38
 *
39
 * @var array
40
 * @access public
41
 */
42
	var $helpers = array('Html');
43
/**
44
 * Holds the fields array('field_name' => array('type'=> 'string', 'length'=> 100),
45
 * primaryKey and validates array('field_name')
46
 *
47
 * @access public
48
 */
49
	var $fieldset = array('fields' => array(), 'key' => 'id', 'validates' => array());
50
/**
51
 * Options used by DateTime fields
52
 *
53
 * @var array
54
 */
55
	var $__options = array(
56
		'day' => array(), 'minute' => array(), 'hour' => array(),
57
		'month' => array(), 'year' => array(), 'meridian' => array()
58
	);
59
/**
60
 * List of fields created, used with secure forms.
61
 *
62
 * @var array
63
 * @access public
64
 */
65
	var $fields = array();
66
/**
67
 * Defines the type of form being created.  Set by FormHelper::create().
68
 *
69
 * @var string
70
 * @access public
71
 */
72
	var $requestType = null;
73
/**
74
 * Returns an HTML FORM element.
75
 *
76
 * @access public
77
 * @param string $model The model object which the form is being defined for
78
 * @param array	 $options
79
 *			'type' Form method defaults to POST
80
 * 			'action'  The Action the form submits to. Can be a string or array,
81
 *			'url'  The url the form submits to. Can be a string or a url array,
82
 *			'default'  Allows for the creation of Ajax forms.
83
 *			'onsubmit' Used in conjunction with 'default' to create ajax forms.
84
 *
85
 * @return string An formatted opening FORM tag.
86
 */
87
	function create($model = null, $options = array()) {
88
		$defaultModel = null;
89
		$view =& ClassRegistry::getObject('view');
90
 
91
		if (is_array($model) && empty($options)) {
92
			$options = $model;
93
			$model = null;
94
		}
95
 
96
		if (empty($model) && $model !== false && !empty($this->params['models'])) {
97
			$model = $this->params['models'][0];
98
			$defaultModel = $this->params['models'][0];
99
		} elseif (empty($model) && empty($this->params['models'])) {
100
			$model = false;
101
		} elseif (is_string($model) && strpos($model, '.') !== false) {
102
			$path = explode('.', $model);
103
			$model = $path[count($path) - 1];
104
		}
105
 
106
		if (ClassRegistry::isKeySet($model)) {
107
			$object =& ClassRegistry::getObject($model);
108
		}
109
 
110
		$models = ClassRegistry::keys();
111
		foreach ($models as $currentModel) {
112
			if (ClassRegistry::isKeySet($currentModel)) {
113
				$currentObject =& ClassRegistry::getObject($currentModel);
114
				if (is_a($currentObject, 'Model') && !empty($currentObject->validationErrors)) {
115
					$this->validationErrors[Inflector::camelize($currentModel)] =& $currentObject->validationErrors;
116
				}
117
			}
118
		}
119
 
120
		$this->setEntity($model . '.', true);
121
		$append = '';
122
		$created = $id = false;
123
 
124
		if (isset($object)) {
125
			$fields = $object->schema();
126
			foreach ($fields as $key => $value) {
127
				unset($fields[$key]);
128
				$fields[$model . '.' . $key] = $value;
129
			}
130
 
131
			if (!empty($object->hasAndBelongsToMany)) {
132
				foreach ($object->hasAndBelongsToMany as $alias => $assocData) {
133
					$fields[$alias] = array('type' => 'multiple');
134
				}
135
			}
136
			$validates = array();
137
			if (!empty($object->validate)) {
138
				foreach ($object->validate as $validateField => $validateProperties) {
139
					if (is_array($validateProperties)) {
140
						$dims = Set::countDim($validateProperties);
141
						if (($dims == 1 && !isset($validateProperties['required']) || (array_key_exists('required', $validateProperties) && $validateProperties['required'] !== false))) {
142
							$validates[] = $validateField;
143
						} elseif ($dims > 1) {
144
							foreach ($validateProperties as $rule => $validateProp) {
145
								if (is_array($validateProp) && (array_key_exists('required', $validateProp) && $validateProp['required'] !== false)) {
146
									$validates[] = $validateField;
147
								}
148
							}
149
						}
150
					}
151
				}
152
			}
153
			$key = $object->primaryKey;
154
			$this->fieldset = compact('fields', 'key', 'validates');
155
		}
156
 
157
		$data = $this->fieldset;
158
		$recordExists = (
159
			isset($this->data[$model]) &&
160
			isset($this->data[$model][$data['key']]) &&
161
			!empty($this->data[$model][$data['key']])
162
		);
163
 
164
		if ($recordExists) {
165
			$created = true;
166
			$id = $this->data[$model][$data['key']];
167
		}
168
		$options = array_merge(array(
169
			'type' => ($created && empty($options['action'])) ? 'put' : 'post',
170
			'action' => null,
171
			'url' => null,
172
			'default' => true),
173
		$options);
174
 
175
		if (empty($options['url']) || is_array($options['url'])) {
176
			if (empty($options['url']['controller'])) {
177
				if (!empty($model) && $model != $defaultModel) {
178
					$options['url']['controller'] = Inflector::underscore(Inflector::pluralize($model));
179
				} elseif (!empty($this->params['controller'])) {
180
					$options['url']['controller'] = Inflector::underscore($this->params['controller']);
181
				}
182
			}
183
			if (empty($options['action'])) {
184
				$options['action'] = ($created) ? 'edit' : 'add';
185
			}
186
 
187
			$actionDefaults = array(
188
				'plugin' => $this->plugin,
189
				'controller' => $view->viewPath,
190
				'action' => $options['action'],
191
				'id' => $id
192
			);
193
			if (!empty($options['action']) && !isset($options['id'])) {
194
				$options['id'] = $model . Inflector::camelize($options['action']) . 'Form';
195
			}
196
			$options['action'] = array_merge($actionDefaults, (array)$options['url']);
197
		} elseif (is_string($options['url'])) {
198
			$options['action'] = $options['url'];
199
		}
200
		unset($options['url']);
201
 
202
		switch (strtolower($options['type'])) {
203
			case 'get':
204
				$htmlAttributes['method'] = 'get';
205
			break;
206
			case 'file':
207
				$htmlAttributes['enctype'] = 'multipart/form-data';
208
				$options['type'] = ($created) ? 'put' : 'post';
209
			case 'post':
210
			case 'put':
211
			case 'delete':
212
				$append .= $this->hidden('_method', array(
213
					'name' => '_method', 'value' => strtoupper($options['type']), 'id' => null
214
				));
215
			default:
216
				$htmlAttributes['method'] = 'post';
217
			break;
218
		}
219
		$this->requestType = strtolower($options['type']);
220
 
221
		$htmlAttributes['action'] = $this->url($options['action']);
222
		unset($options['type'], $options['action']);
223
 
224
		if ($options['default'] == false) {
225
			if (isset($htmlAttributes['onSubmit']) || isset($htmlAttributes['onsubmit'])) {
226
				$htmlAttributes['onsubmit'] .= ' event.returnValue = false; return false;';
227
			} else {
228
				$htmlAttributes['onsubmit'] = 'event.returnValue = false; return false;';
229
			}
230
		}
231
		unset($options['default']);
232
		$htmlAttributes = array_merge($options, $htmlAttributes);
233
 
234
		if (isset($this->params['_Token']) && !empty($this->params['_Token'])) {
235
			$append .= $this->hidden('_Token.key', array(
236
				'value' => $this->params['_Token']['key'], 'id' => 'Token' . mt_rand())
237
			);
238
		}
239
 
240
		if (!empty($append)) {
241
			$append = sprintf($this->Html->tags['fieldset'], ' style="display:none;"', $append);
242
		}
243
 
244
		$this->setEntity($model . '.', true);
245
		$attributes = $this->_parseAttributes($htmlAttributes, null, '');
246
		return $this->output(sprintf($this->Html->tags['form'], $attributes)) . $append;
247
	}
248
/**
249
 * Closes an HTML form, cleans up values set by FormHelper::create(), and writes hidden
250
 * input fields where appropriate.
251
 *
252
 * If $options is set a form submit button will be created.
253
 *
254
 * @param mixed $options as a string will use $options as the value of button,
255
 * 	array usage:
256
 * 		array('label' => 'save'); value="save"
257
 * 		array('label' => 'save', 'name' => 'Whatever'); value="save" name="Whatever"
258
 * 		array('name' => 'Whatever'); value="Submit" name="Whatever"
259
 * 		array('label' => 'save', 'name' => 'Whatever', 'div' => 'good') <div class="good"> value="save" name="Whatever"
260
 * 		array('label' => 'save', 'name' => 'Whatever', 'div' => array('class' => 'good')); <div class="good"> value="save" name="Whatever"
261
 *
262
 * @return string a closing FORM tag optional submit button.
263
 * @access public
264
 */
265
	function end($options = null) {
266
		if (!empty($this->params['models'])) {
267
			$models = $this->params['models'][0];
268
		}
269
		$out = null;
270
		$submit = null;
271
 
272
		if ($options !== null) {
273
			$submitOptions = array();
274
			if (is_string($options)) {
275
				$submit = $options;
276
			} else {
277
				if (isset($options['label'])) {
278
					$submit = $options['label'];
279
					unset($options['label']);
280
				}
281
				$submitOptions = $options;
282
 
283
				if (!$submit) {
284
					$submit = __('Submit', true);
285
				}
286
			}
287
			$out .= $this->submit($submit, $submitOptions);
288
		}
289
		if (isset($this->params['_Token']) && !empty($this->params['_Token'])) {
290
			$out .= $this->secure($this->fields);
291
			$this->fields = array();
292
		}
293
		$this->setEntity(null);
294
		$out .= $this->Html->tags['formend'];
295
 
296
		$view =& ClassRegistry::getObject('view');
297
		$view->modelScope = false;
298
		return $this->output($out);
299
	}
300
/**
301
 * Generates a hidden field with a security hash based on the fields used in the form.
302
 *
303
 * @param array $fields The list of fields to use when generating the hash
304
 * @return string A hidden input field with a security hash
305
 * @access public
306
 */
307
	function secure($fields = array()) {
308
		if (!isset($this->params['_Token']) || empty($this->params['_Token'])) {
309
			return;
310
		}
311
		$out = '<fieldset style="display:none;">';
312
		$locked = array();
313
 
314
		foreach ($fields as $key => $value) {
315
			if (!is_int($key)) {
316
				$locked[$key] = $value;
317
				unset($fields[$key]);
318
			}
319
		}
320
		sort($fields, SORT_STRING);
321
		ksort($locked, SORT_STRING);
322
		$fields += $locked;
323
 
324
		$fields = Security::hash(serialize($fields) . Configure::read('Security.salt'));
325
		$locked = str_rot13(serialize(array_keys($locked)));
326
 
327
		$out .= $this->hidden('_Token.fields', array(
328
			'value' => urlencode($fields . ':' . $locked),
329
			'id' => 'TokenFields' . mt_rand()
330
		));
331
		return $out .= '</fieldset>';
332
	}
333
/**
334
 * Determine which fields of a form should be used for hash
335
 *
336
 * @param mixed $field Reference to field to be secured
337
 * @param mixed $value Field value, if value should not be tampered with
338
 * @access private
339
 */
340
	function __secure($field = null, $value = null) {
341
		if (!$field) {
342
			$view =& ClassRegistry::getObject('view');
343
			$field = $view->entity();
344
		} elseif (is_string($field)) {
345
			$field = Set::filter(explode('.', $field), true);
346
		}
347
 
348
		if (!empty($this->params['_Token']['disabledFields'])) {
349
			foreach ((array)$this->params['_Token']['disabledFields'] as $disabled) {
350
				$disabled = explode('.', $disabled);
351
				if (array_values(array_intersect($field, $disabled)) === $disabled) {
352
					return;
353
				}
354
			}
355
		}
356
		$field = join('.', $field);
357
		if (!in_array($field, $this->fields)) {
358
			if ($value !== null) {
359
				return $this->fields[$field] = $value;
360
			}
361
			$this->fields[] = $field;
362
		}
363
	}
364
/**
365
 * Returns true if there is an error for the given field, otherwise false
366
 *
367
 * @param string $field This should be "Modelname.fieldname"
368
 * @return boolean If there are errors this method returns true, else false.
369
 * @access public
370
 */
371
	function isFieldError($field) {
372
		$this->setEntity($field);
373
		return (bool)$this->tagIsInvalid();
374
	}
375
/**
376
 * Returns a formatted error message for given FORM field, NULL if no errors.
377
 *
378
 * @param string $field A field name, like "Modelname.fieldname"
379
 * @param mixed $text		Error message or array of $options
380
 * @param array $options	Rendering options for <div /> wrapper tag
381
 *			'escape'  bool  Whether or not to html escape the contents of the error.
382
 *			'wrap'  mixed  Whether or not the error message should be wrapped in a div. If a
383
 * 					string, will be used as the HTML tag to use.
384
 *			'class'  string  The classname for the error message
385
 * @return string If there are errors this method returns an error message, otherwise null.
386
 * @access public
387
 */
388
	function error($field, $text = null, $options = array()) {
389
		$defaults = array('wrap' => true, 'class' => 'error-message', 'escape' => true);
390
		$options = array_merge($defaults, $options);
391
		$this->setEntity($field);
392
 
393
		if ($error = $this->tagIsInvalid()) {
394
			if (is_array($error)) {
395
				list(,,$field) = explode('.', $field);
396
				if (isset($error[$field])) {
397
					$error = $error[$field];
398
				} else {
399
					return null;
400
				}
401
			}
402
 
403
			if (is_array($text) && is_numeric($error) && $error > 0) {
404
				$error--;
405
			}
406
			if (is_array($text) && isset($text[$error])) {
407
				$text = $text[$error];
408
			} elseif (is_array($text)) {
409
				$options = array_merge($options, $text);
410
				$text = null;
411
			}
412
 
413
			if ($text != null) {
414
				$error = $text;
415
			} elseif (is_numeric($error)) {
416
				$error = sprintf(__('Error in field %s', true), Inflector::humanize($this->field()));
417
			}
418
			if ($options['escape']) {
419
				$error = h($error);
420
				unset($options['escape']);
421
			}
422
			if ($options['wrap']) {
423
				$tag = is_string($options['wrap']) ? $options['wrap'] : 'div';
424
				unset($options['wrap']);
425
				return $this->Html->tag($tag, $error, $options);
426
			} else {
427
				return $error;
428
			}
429
		} else {
430
			return null;
431
		}
432
	}
433
/**
434
 * Returns a formatted LABEL element for HTML FORMs.
435
 *
436
 * @param string $fieldName This should be "Modelname.fieldname"
437
 * @param string $text Text that will appear in the label field.
438
 * @param array $attributes Array of HTML attributes.
439
 * @return string The formatted LABEL element
440
 */
441
	function label($fieldName = null, $text = null, $attributes = array()) {
442
		if (empty($fieldName)) {
443
			$view = ClassRegistry::getObject('view');
444
			$fieldName = implode('.', $view->entity());
445
		}
446
 
447
		if ($text === null) {
448
			if (strpos($fieldName, '.') !== false) {
449
				$text = array_pop(explode('.', $fieldName));
450
			} else {
451
				$text = $fieldName;
452
			}
453
			if (substr($text, -3) == '_id') {
454
				$text = substr($text, 0, strlen($text) - 3);
455
			}
456
			$text = __(Inflector::humanize(Inflector::underscore($text)), true);
457
		}
458
 
459
		if (isset($attributes['for'])) {
460
			$labelFor = $attributes['for'];
461
			unset($attributes['for']);
462
		} else {
463
			$labelFor = $this->domId($fieldName);
464
		}
465
 
466
		return $this->output(sprintf(
467
			$this->Html->tags['label'],
468
			$labelFor,
469
			$this->_parseAttributes($attributes), $text
470
		));
471
	}
472
/**
473
 * Will display all the fields passed in an array expects fieldName as an array key
474
 * replaces generateFields
475
 *
476
 * @access public
477
 * @param array $fields works well with Controller::generateFields() or on its own;
478
 * @param array $blacklist a simple array of fields to skip
479
 * @return output
480
 */
481
	function inputs($fields = null, $blacklist = null) {
482
		$fieldset = $legend = true;
483
 
484
		if (is_array($fields)) {
485
			if (array_key_exists('legend', $fields)) {
486
				$legend = $fields['legend'];
487
				unset($fields['legend']);
488
			}
489
 
490
			if (isset($fields['fieldset'])) {
491
				$fieldset = $fields['fieldset'];
492
				unset($fields['fieldset']);
493
			}
494
		} elseif ($fields !== null) {
495
			$fieldset = $legend = $fields;
496
			if (!is_bool($fieldset)) {
497
				$fieldset = true;
498
			}
499
			$fields = array();
500
		}
501
 
502
		if (empty($fields)) {
503
			$fields = array_keys($this->fieldset['fields']);
504
		}
505
 
506
		if ($legend === true) {
507
			$actionName = __('New', true);
508
			$isEdit = (
509
				strpos($this->action, 'update') !== false ||
510
				strpos($this->action, 'edit') !== false
511
			);
512
			if ($isEdit) {
513
				$actionName = __('Edit', true);
514
			}
515
			$modelName = Inflector::humanize(Inflector::underscore($this->model()));
516
			$legend = $actionName .' '. __($modelName, true);
517
		}
518
 
519
		$out = null;
520
		foreach ($fields as $name => $options) {
521
			if (is_numeric($name) && !is_array($options)) {
522
				$name = $options;
523
				$options = array();
524
			}
525
			$entity = explode('.', $name);
526
			$blacklisted = (
527
				is_array($blacklist) &&
528
				(in_array($name, $blacklist) || in_array(end($entity), $blacklist))
529
			);
530
			if ($blacklisted) {
531
				continue;
532
			}
533
			$out .= $this->input($name, $options);
534
		}
535
 
536
		if (is_string($fieldset)) {
537
			$fieldsetClass = sprintf(' class="%s"', $fieldset);
538
		} else {
539
			$fieldsetClass = '';
540
		}
541
 
542
		if ($fieldset && $legend) {
543
			return sprintf(
544
				$this->Html->tags['fieldset'],
545
				$fieldsetClass,
546
				sprintf($this->Html->tags['legend'], $legend) . $out
547
			);
548
		} elseif ($fieldset) {
549
			return sprintf($this->Html->tags['fieldset'], $fieldsetClass, $out);
550
		} else {
551
			return $out;
552
		}
553
	}
554
/**
555
 * Generates a form input element complete with label and wrapper div
556
 *
557
 * @param string $fieldName This should be "Modelname.fieldname"
558
 * @param array $options - Each type of input takes different options.
559
 * 		See each field type method for more information.
560
 *		'type' - Force the type of widget you want. e.g. type => 'select'
561
 *		'label' - control the label
562
 *		'div' - control the wrapping div element
563
 *		'options' - for widgets that take options e.g. radio, select
564
 * 		'error' - control the error message that is produced
565
 *
566
 * @return string
567
 */
568
	function input($fieldName, $options = array()) {
569
		$view =& ClassRegistry::getObject('view');
570
		$this->setEntity($fieldName);
571
		$entity = join('.', $view->entity());
572
 
573
		$defaults = array('before' => null, 'between' => null, 'after' => null);
574
		$options = array_merge($defaults, $options);
575
 
576
		if (!isset($options['type'])) {
577
			$options['type'] = 'text';
578
 
579
			if (isset($options['options'])) {
580
				$options['type'] = 'select';
581
			} elseif (in_array($this->field(), array('psword', 'passwd', 'password'))) {
582
				$options['type'] = 'password';
583
			} elseif (isset($this->fieldset['fields'][$entity])) {
584
				$fieldDef = $this->fieldset['fields'][$entity];
585
				$type = $fieldDef['type'];
586
				$primaryKey = $this->fieldset['key'];
587
			} elseif (ClassRegistry::isKeySet($this->model())) {
588
				$model =& ClassRegistry::getObject($this->model());
589
				$type = $model->getColumnType($this->field());
590
				$fieldDef = $model->schema();
591
 
592
				if (isset($fieldDef[$this->field()])) {
593
					$fieldDef = $fieldDef[$this->field()];
594
				} else {
595
					$fieldDef = array();
596
				}
597
				$primaryKey = $model->primaryKey;
598
			}
599
 
600
			if (isset($type)) {
601
				$map = array(
602
					'string'  => 'text',     'datetime'  => 'datetime',
603
					'boolean' => 'checkbox', 'timestamp' => 'datetime',
604
					'text'    => 'textarea', 'time'      => 'time',
605
					'date'    => 'date',     'float'     => 'text'
606
				);
607
 
608
				if (isset($this->map[$type])) {
609
					$options['type'] = $this->map[$type];
610
				} elseif (isset($map[$type])) {
611
					$options['type'] = $map[$type];
612
				}
613
				if ($this->field() == $primaryKey) {
614
					$options['type'] = 'hidden';
615
				}
616
			}
617
 
618
			if ($this->model() === $this->field()) {
619
				$options['type'] = 'select';
620
				if (!isset($options['multiple'])) {
621
					$options['multiple'] = 'multiple';
622
				}
623
			}
624
		}
625
		$types = array('text', 'checkbox', 'radio', 'select');
626
 
627
		if (!isset($options['options']) && in_array($options['type'], $types)) {
628
			$view =& ClassRegistry::getObject('view');
629
			$varName = Inflector::variable(
630
				Inflector::pluralize(preg_replace('/_id$/', '', $this->field()))
631
			);
632
			$varOptions = $view->getVar($varName);
633
			if (is_array($varOptions)) {
634
				if ($options['type'] !== 'radio') {
635
					$options['type'] = 'select';
636
				}
637
				$options['options'] = $varOptions;
638
			}
639
		}
640
 
641
		$autoLength = (!array_key_exists('maxlength', $options) && isset($fieldDef['length']));
642
		if ($autoLength && $options['type'] == 'text') {
643
			$options['maxlength'] = $fieldDef['length'];
644
		}
645
		if ($autoLength && $fieldDef['type'] == 'float') {
646
			$options['maxlength'] = array_sum(explode(',', $fieldDef['length']))+1;
647
		}
648
 
649
		$out = '';
650
		$div = true;
651
		$divOptions = array();
652
 
653
		if (array_key_exists('div', $options)) {
654
			$div = $options['div'];
655
			unset($options['div']);
656
		}
657
 
658
		if (!empty($div)) {
659
			$divOptions['class'] = 'input';
660
			$divOptions = $this->addClass($divOptions, $options['type']);
661
			if (is_string($div)) {
662
				$divOptions['class'] = $div;
663
			} elseif (is_array($div)) {
664
				$divOptions = array_merge($divOptions, $div);
665
			}
666
			if (in_array($this->field(), $this->fieldset['validates'])) {
667
				$divOptions = $this->addClass($divOptions, 'required');
668
			}
669
			if (!isset($divOptions['tag'])) {
670
				$divOptions['tag'] = 'div';
671
			}
672
		}
673
 
674
		$label = null;
675
		if (isset($options['label']) && $options['type'] !== 'radio') {
676
			$label = $options['label'];
677
			unset($options['label']);
678
		}
679
 
680
		if ($options['type'] === 'radio') {
681
			$label = false;
682
			if (isset($options['options'])) {
683
				if (is_array($options['options'])) {
684
					$radioOptions = $options['options'];
685
				} else {
686
					$radioOptions = array($options['options']);
687
				}
688
				unset($options['options']);
689
			}
690
		}
691
 
692
		if ($label !== false) {
693
			$labelAttributes = $this->domId(array(), 'for');
694
			if (in_array($options['type'], array('date', 'datetime'))) {
695
				$labelAttributes['for'] .= 'Month';
696
			} else if ($options['type'] === 'time') {
697
				$labelAttributes['for'] .= 'Hour';
698
			}
699
 
700
			if (is_array($label)) {
701
				$labelText = null;
702
				if (isset($label['text'])) {
703
					$labelText = $label['text'];
704
					unset($label['text']);
705
				}
706
				$labelAttributes = array_merge($labelAttributes, $label);
707
			} else {
708
				$labelText = $label;
709
			}
710
 
711
			if (isset($options['id'])) {
712
				$labelAttributes = array_merge($labelAttributes, array('for' => $options['id']));
713
			}
714
			$out = $this->label($fieldName, $labelText, $labelAttributes);
715
		}
716
 
717
		$error = null;
718
		if (isset($options['error'])) {
719
			$error = $options['error'];
720
			unset($options['error']);
721
		}
722
 
723
		$selected = null;
724
		if (array_key_exists('selected', $options)) {
725
			$selected = $options['selected'];
726
			unset($options['selected']);
727
		}
728
		if (isset($options['rows']) || isset($options['cols'])) {
729
			$options['type'] = 'textarea';
730
		}
731
 
732
		$empty = false;
733
		if (isset($options['empty'])) {
734
			$empty = $options['empty'];
735
			unset($options['empty']);
736
		}
737
 
738
		$timeFormat = 12;
739
		if (isset($options['timeFormat'])) {
740
			$timeFormat = $options['timeFormat'];
741
			unset($options['timeFormat']);
742
		}
743
 
744
		$dateFormat = 'MDY';
745
		if (isset($options['dateFormat'])) {
746
			$dateFormat = $options['dateFormat'];
747
			unset($options['dateFormat']);
748
		}
749
 
750
		$type	 = $options['type'];
751
		$before	 = $options['before'];
752
		$between = $options['between'];
753
		$after	 = $options['after'];
754
		unset($options['type'], $options['before'], $options['between'], $options['after']);
755
 
756
		switch ($type) {
757
			case 'hidden':
758
				$out = $this->hidden($fieldName, $options);
759
				unset($divOptions);
760
			break;
761
			case 'checkbox':
762
				$out = $before . $this->checkbox($fieldName, $options) . $between . $out;
763
			break;
764
			case 'radio':
765
				$out = $before . $out . $this->radio($fieldName, $radioOptions, $options) . $between;
766
			break;
767
			case 'text':
768
			case 'password':
769
				$out = $before . $out . $between . $this->{$type}($fieldName, $options);
770
			break;
771
			case 'file':
772
				$out = $before . $out . $between . $this->file($fieldName, $options);
773
			break;
774
			case 'select':
775
				$options = array_merge(array('options' => array()), $options);
776
				$list = $options['options'];
777
				unset($options['options']);
778
				$out = $before . $out . $between . $this->select(
779
					$fieldName, $list, $selected, $options, $empty
780
				);
781
			break;
782
			case 'time':
783
				$out = $before . $out . $between . $this->dateTime(
784
					$fieldName, null, $timeFormat, $selected, $options, $empty
785
				);
786
			break;
787
			case 'date':
788
				$out = $before . $out . $between . $this->dateTime(
789
					$fieldName, $dateFormat, null, $selected, $options, $empty
790
				);
791
			break;
792
			case 'datetime':
793
				$out = $before . $out . $between . $this->dateTime(
794
					$fieldName, $dateFormat, $timeFormat, $selected, $options, $empty
795
				);
796
			break;
797
			case 'textarea':
798
			default:
799
				$out = $before . $out . $between . $this->textarea($fieldName, array_merge(
800
					array('cols' => '30', 'rows' => '6'), $options
801
				));
802
			break;
803
		}
804
 
805
		if ($type != 'hidden') {
806
			$out .= $after;
807
			if ($error !== false) {
808
				$errMsg = $this->error($fieldName, $error);
809
				if ($errMsg) {
810
					$out .= $errMsg;
811
					$divOptions = $this->addClass($divOptions, 'error');
812
				}
813
			}
814
		}
815
		if (isset($divOptions) && isset($divOptions['tag'])) {
816
			$tag = $divOptions['tag'];
817
			unset($divOptions['tag']);
818
			$out = $this->Html->tag($tag, $out, $divOptions);
819
		}
820
		return $out;
821
	}
822
/**
823
 * Creates a checkbox input widget.
824
 *
825
 * @param string $fieldName Name of a field, like this "Modelname.fieldname"
826
 * @param array $options Array of HTML attributes.
827
 *		'value' - the value of the checkbox
828
 *		'checked' - boolean indicate that this checkbox is checked.
829
 * @todo Right now, automatically setting the 'checked' value is dependent on whether or not the
830
 * 		 checkbox is bound to a model.  This should probably be re-evaluated in future versions.
831
 * @return string An HTML text input element
832
 */
833
	function checkbox($fieldName, $options = array()) {
834
		$options = $this->_initInputField($fieldName, $options);
835
		$value = current($this->value());
836
 
837
		if (!isset($options['value']) || empty($options['value'])) {
838
			$options['value'] = 1;
839
		} elseif (!empty($value) && $value === $options['value']) {
840
			$options['checked'] = 'checked';
841
		}
842
 
843
		$output = $this->hidden($fieldName, array(
844
			'id' => $options['id'] . '_', 'name' => $options['name'],
845
			'value' => '0', 'secure' => false
846
		));
847
 
848
		return $this->output($output . sprintf(
849
			$this->Html->tags['checkbox'],
850
			$options['name'],
851
			$this->_parseAttributes($options, array('name'), null, ' ')
852
		));
853
	}
854
/**
855
 * Creates a set of radio widgets.
856
 *
857
 * @param  string  	$fieldName 		Name of a field, like this "Modelname.fieldname"
858
 * @param  array	$options		Radio button options array.
859
 * @param  array	$attributes		Array of HTML attributes.
860
 *		'separator' - define the string in between the radio buttons
861
 *		'legend' - control whether or not the widget set has a fieldset & legend
862
 *		'value' - indicate a value that is should be checked
863
 * 		'label' - boolean to indicate whether or not labels for widgets show be displayed
864
 *
865
 * @return string
866
 */
867
	function radio($fieldName, $options = array(), $attributes = array()) {
868
		$attributes = $this->_initInputField($fieldName, $attributes);
869
		$legend = false;
870
 
871
		if (isset($attributes['legend'])) {
872
			$legend = $attributes['legend'];
873
			unset($attributes['legend']);
874
		} elseif (count($options) > 1) {
875
			$legend = __(Inflector::humanize($this->field()), true);
876
		}
877
		$label = true;
878
 
879
		if (isset($attributes['label'])) {
880
			$label = $attributes['label'];
881
			unset($attributes['label']);
882
		}
883
		$inbetween = null;
884
 
885
		if (isset($attributes['separator'])) {
886
			$inbetween = $attributes['separator'];
887
			unset($attributes['separator']);
888
		}
889
 
890
		if (isset($attributes['value'])) {
891
			$value = $attributes['value'];
892
		} else {
893
			$value =  $this->value($fieldName);
894
		}
895
		$out = array();
896
 
897
		foreach ($options as $optValue => $optTitle) {
898
			$optionsHere = array('value' => $optValue);
899
 
900
			if (isset($value) && $optValue == $value) {
901
				$optionsHere['checked'] = 'checked';
902
			}
903
			$parsedOptions = $this->_parseAttributes(
904
				array_merge($attributes, $optionsHere),
905
				array('name', 'type', 'id'), '', ' '
906
			);
907
			$tagName = Inflector::camelize(
908
				$attributes['id'] . '_' . Inflector::underscore($optValue)
909
			);
910
 
911
			if ($label) {
912
				$optTitle =  sprintf($this->Html->tags['label'], $tagName, null, $optTitle);
913
			}
914
			$out[] =  sprintf(
915
				$this->Html->tags['radio'], $attributes['name'],
916
				$tagName, $parsedOptions, $optTitle
917
			);
918
		}
919
		$hidden = null;
920
 
921
		if (!isset($value) || $value === '') {
922
			$hidden = $this->hidden($fieldName, array(
923
				'id' => $attributes['id'] . '_', 'value' => ''
924
			));
925
		}
926
		$out = $hidden . join($inbetween, $out);
927
 
928
		if ($legend) {
929
			$out = sprintf(
930
				$this->Html->tags['fieldset'], '',
931
				sprintf($this->Html->tags['legend'], $legend) . $out
932
			);
933
		}
934
		return $this->output($out);
935
	}
936
/**
937
 * Creates a text input widget.
938
 *
939
 * @param string $fieldNamem Name of a field, in the form "Modelname.fieldname"
940
 * @param array  $options Array of HTML attributes.
941
 * @return string An HTML text input element
942
 */
943
	function text($fieldName, $options = array()) {
944
		$options = $this->_initInputField($fieldName, array_merge(
945
			array('type' => 'text'), $options
946
		));
947
		return $this->output(sprintf(
948
			$this->Html->tags['input'],
949
			$options['name'],
950
			$this->_parseAttributes($options, array('name'), null, ' ')
951
		));
952
	}
953
/**
954
 * Creates a password input widget.
955
 *
956
 * @param  string  $fieldName Name of a field, like in the form "Modelname.fieldname"
957
 * @param  array	$options Array of HTML attributes.
958
 * @return string
959
 */
960
	function password($fieldName, $options = array()) {
961
		$options = $this->_initInputField($fieldName, $options);
962
		return $this->output(sprintf(
963
			$this->Html->tags['password'],
964
			$options['name'],
965
			$this->_parseAttributes($options, array('name'), null, ' ')
966
		));
967
	}
968
/**
969
 * Creates a textarea widget.
970
 *
971
 * @param string $fieldNamem Name of a field, in the form "Modelname.fieldname"
972
 * @param array $options Array of HTML attributes.
973
 * @return string An HTML text input element
974
 */
975
	function textarea($fieldName, $options = array()) {
976
		$options = $this->_initInputField($fieldName, $options);
977
		$value = null;
978
 
979
		if (array_key_exists('value', $options)) {
980
			$value = $options['value'];
981
			if (!array_key_exists('escape', $options) || $options['escape'] !== false) {
982
				$value = h($value);
983
			}
984
			unset($options['value']);
985
		}
986
		return $this->output(sprintf(
987
			$this->Html->tags['textarea'],
988
			$options['name'],
989
			$this->_parseAttributes($options, array('type', 'name'), null, ' '),
990
			$value
991
		));
992
	}
993
/**
994
 * Creates a hidden input field.
995
 *
996
 * @param  string  $fieldName Name of a field, in the form"Modelname.fieldname"
997
 * @param  array	$options Array of HTML attributes.
998
 * @return string
999
 * @access public
1000
 */
1001
	function hidden($fieldName, $options = array()) {
1002
		$secure = true;
1003
 
1004
		if (isset($options['secure'])) {
1005
			$secure = $options['secure'];
1006
			unset($options['secure']);
1007
		}
1008
		$options = $this->_initInputField($fieldName, array_merge(
1009
			$options, array('secure' => false)
1010
		));
1011
		$model = $this->model();
1012
 
1013
		if ($fieldName !== '_method' && $model !== '_Token' && $secure) {
1014
			$this->__secure(null, '' . $options['value']);
1015
		}
1016
 
1017
		return $this->output(sprintf(
1018
			$this->Html->tags['hidden'],
1019
			$options['name'],
1020
			$this->_parseAttributes($options, array('name', 'class'), '', ' ')
1021
		));
1022
	}
1023
/**
1024
 * Creates file input widget.
1025
 *
1026
 * @param string $fieldName Name of a field, in the form "Modelname.fieldname"
1027
 * @param array $options Array of HTML attributes.
1028
 * @return string
1029
 * @access public
1030
 */
1031
	function file($fieldName, $options = array()) {
1032
		$options = array_merge($options, array('secure' => false));
1033
		$options = $this->_initInputField($fieldName, $options);
1034
		$view =& ClassRegistry::getObject('view');
1035
		$field = $view->entity();
1036
 
1037
		foreach (array('name', 'type', 'tmp_name', 'error', 'size') as $suffix) {
1038
			$this->__secure(array_merge($field, array($suffix)));
1039
		}
1040
 
1041
		$attributes = $this->_parseAttributes($options, array('name'), '', ' ');
1042
		return $this->output(sprintf($this->Html->tags['file'], $options['name'], $attributes));
1043
	}
1044
/**
1045
 * Creates a button tag.
1046
 *
1047
 * @param  string  $title  The button's caption
1048
 * @param  array  $options Array of options.
1049
 * @return string A HTML button tag.
1050
 * @access public
1051
 */
1052
	function button($title, $options = array()) {
1053
		$options = array_merge(array('type' => 'button', 'value' => $title), $options);
1054
 
1055
		if (isset($options['name']) && strpos($options['name'], '.') !== false) {
1056
			if ($this->value($options['name'])) {
1057
				$options['checked'] = 'checked';
1058
			}
1059
			$name = $options['name'];
1060
			unset($options['name']);
1061
			$options = $this->_initInputField($name, $options);
1062
		}
1063
		return $this->output(sprintf(
1064
			$this->Html->tags['button'],
1065
			$options['type'],
1066
			$this->_parseAttributes($options, array('type'), '', ' ')
1067
		));
1068
	}
1069
/**
1070
 * Creates a submit button element.
1071
 *
1072
 * @param  string  $caption	 The label appearing on the button OR if string contains :// or the
1073
 * 						extension .jpg, .jpe, .jpeg, .gif, .png use an image if the extension
1074
 * 						exists, AND the first character is /, image is relative to webroot,
1075
 *						OR if the first character is not /, image is relative to webroot/img.
1076
 * @param  array   $options
1077
 * @return string A HTML submit button
1078
 */
1079
	function submit($caption = null, $options = array()) {
1080
		if (!$caption) {
1081
			$caption = __('Submit', true);
1082
		}
1083
		$out = null;
1084
		$div = true;
1085
 
1086
		if (isset($options['div'])) {
1087
			$div = $options['div'];
1088
			unset($options['div']);
1089
		}
1090
		$divOptions = array('tag' => 'div');
1091
 
1092
		if ($div === true) {
1093
			$divOptions['class'] = 'submit';
1094
		} elseif ($div === false) {
1095
			unset($divOptions);
1096
		} elseif (is_string($div)) {
1097
			$divOptions['class'] = $div;
1098
		} elseif (is_array($div)) {
1099
			$divOptions = array_merge(array('class' => 'submit', 'tag' => 'div'), $div);
1100
		}
1101
 
1102
		if (strpos($caption, '://') !== false) {
1103
			$out .= $this->output(sprintf(
1104
				$this->Html->tags['submitimage'],
1105
				$caption,
1106
				$this->_parseAttributes($options, null, '', ' ')
1107
			));
1108
		} elseif (preg_match('/\.(jpg|jpe|jpeg|gif|png|ico)$/', $caption)) {
1109
			if ($caption{0} !== '/') {
1110
				$url = $this->webroot(IMAGES_URL . $caption);
1111
			} else {
1112
				$caption = trim($caption, '/');
1113
				$url = $this->webroot($caption);
1114
			}
1115
			$out .= $this->output(sprintf(
1116
				$this->Html->tags['submitimage'],
1117
				$url,
1118
				$this->_parseAttributes($options, null, '', ' ')
1119
			));
1120
		} else {
1121
			$options['value'] = $caption;
1122
			$out .= $this->output(sprintf(
1123
				$this->Html->tags['submit'],
1124
				$this->_parseAttributes($options, null, '', ' ')
1125
			));
1126
		}
1127
 
1128
		if (isset($divOptions)) {
1129
			$tag = $divOptions['tag'];
1130
			unset($divOptions['tag']);
1131
			$out = $this->Html->tag($tag, $out, $divOptions);
1132
		}
1133
		return $out;
1134
	}
1135
/**
1136
 * Returns a formatted SELECT element.
1137
 *
1138
 * @param string $fieldName Name attribute of the SELECT
1139
 * @param array $options Array of the OPTION elements (as 'value'=>'Text' pairs) to be used in the
1140
 * 						 SELECT element
1141
 * @param mixed $selected The option selected by default.  If null, the default value
1142
 *						  from POST data will be used when available.
1143
 * @param array $attributes	 The HTML attributes of the select element.
1144
 *		'showParents' - If included in the array and set to true, an additional option element
1145
 *						will be added for the parent of each option group.
1146
 *		'multiple' - show a multiple select box.  If set to 'checkbox' multiple checkboxes will be
1147
 * 					 created instead.
1148
 * @param mixed $showEmpty If true, the empty select option is shown.  If a string,
1149
 *						   that string is displayed as the empty element.
1150
 * @return string Formatted SELECT element
1151
 */
1152
	function select($fieldName, $options = array(), $selected = null, $attributes = array(), $showEmpty = '') {
1153
		$select = array();
1154
		$showParents = false;
1155
		$escapeOptions = true;
1156
		$style = null;
1157
		$tag = null;
1158
 
1159
		if (isset($attributes['escape'])) {
1160
			$escapeOptions = $attributes['escape'];
1161
			unset($attributes['escape']);
1162
		}
1163
		$attributes = $this->_initInputField($fieldName, array_merge(
1164
			(array)$attributes, array('secure' => false)
1165
		));
1166
 
1167
		if (is_string($options) && isset($this->__options[$options])) {
1168
			$options = $this->__generateOptions($options);
1169
		} elseif (!is_array($options)) {
1170
			$options = array();
1171
		}
1172
		if (isset($attributes['type'])) {
1173
			unset($attributes['type']);
1174
		}
1175
		if (in_array('showParents', $attributes)) {
1176
			$showParents = true;
1177
			unset($attributes['showParents']);
1178
		}
1179
 
1180
		if (!isset($selected)) {
1181
			$selected = $attributes['value'];
1182
		}
1183
 
1184
		if (isset($attributes) && array_key_exists('multiple', $attributes)) {
1185
			$style = ($attributes['multiple'] === 'checkbox') ? 'checkbox' : null;
1186
			$template = ($style) ? 'checkboxmultiplestart' : 'selectmultiplestart';
1187
			$tag = $this->Html->tags[$template];
1188
			$select[] = $this->hidden(null, array('value' => '', 'id' => null, 'secure' => false));
1189
		} else {
1190
			$tag = $this->Html->tags['selectstart'];
1191
		}
1192
 
1193
		if (!empty($tag) || isset($template)) {
1194
			$this->__secure();
1195
			$select[] = sprintf($tag, $attributes['name'], $this->_parseAttributes(
1196
				$attributes, array('name', 'value'))
1197
			);
1198
		}
1199
		$emptyMulti = (
1200
			$showEmpty !== null && $showEmpty !== false && !(
1201
				empty($showEmpty) && (isset($attributes) &&
1202
				array_key_exists('multiple', $attributes))
1203
			)
1204
		);
1205
 
1206
		if ($emptyMulti) {
1207
			$showEmpty = ($showEmpty === true) ? '' : $showEmpty;
1208
			$options = array_reverse($options, true);
1209
			$options[''] = $showEmpty;
1210
			$options = array_reverse($options, true);
1211
		}
1212
 
1213
		$select = array_merge($select, $this->__selectOptions(
1214
			array_reverse($options, true),
1215
			$selected,
1216
			array(),
1217
			$showParents,
1218
			array('escape' => $escapeOptions, 'style' => $style)
1219
		));
1220
 
1221
		$template = ($style == 'checkbox') ? 'checkboxmultipleend' : 'selectend';
1222
		$select[] = $this->Html->tags[$template];
1223
		return $this->output(implode("\n", $select));
1224
	}
1225
/**
1226
 * Returns a SELECT element for days.
1227
 *
1228
 * @param string $fieldName Prefix name for the SELECT element
1229
 * @param string $selected Option which is selected.
1230
 * @param array	 $attributes HTML attributes for the select element
1231
 * @param mixed $showEmpty Show/hide the empty select option
1232
 * @return string
1233
 */
1234
	function day($fieldName, $selected = null, $attributes = array(), $showEmpty = true) {
1235
		if ((empty($selected) || $selected === true) && $value = $this->value($fieldName)) {
1236
			if (is_array($value)) {
1237
				extract($value);
1238
				$selected = $day;
1239
			} else {
1240
				if (empty($value)) {
1241
					if (!$showEmpty) {
1242
						$selected = 'now';
1243
					}
1244
				} else {
1245
					$selected = $value;
1246
				}
1247
			}
1248
		}
1249
 
1250
		if (strlen($selected) > 2) {
1251
			$selected = date('d', strtotime($selected));
1252
		} elseif ($selected === false) {
1253
			$selected = null;
1254
		}
1255
		return $this->select(
1256
			$fieldName . ".day", $this->__generateOptions('day'), $selected, $attributes, $showEmpty
1257
		);
1258
	}
1259
/**
1260
 * Returns a SELECT element for years
1261
 *
1262
 * @param string $fieldName Prefix name for the SELECT element
1263
 * @param integer $minYear First year in sequence
1264
 * @param integer $maxYear Last year in sequence
1265
 * @param string $selected Option which is selected.
1266
 * @param array $attributes Attribute array for the select elements.
1267
 * @param boolean $showEmpty Show/hide the empty select option
1268
 * @return string
1269
 */
1270
	function year($fieldName, $minYear = null, $maxYear = null, $selected = null, $attributes = array(), $showEmpty = true) {
1271
		if ((empty($selected) || $selected === true) && $value = $this->value($fieldName)) {
1272
			if (is_array($value)) {
1273
				extract($value);
1274
				$selected = $year;
1275
			} else {
1276
				if (empty($value)) {
1277
					if (!$showEmpty && !$maxYear) {
1278
						$selected = 'now';
1279
 
1280
					} elseif (!$showEmpty && $maxYear && !$selected) {
1281
						$selected = $maxYear;
1282
					}
1283
				} else {
1284
					$selected = $value;
1285
				}
1286
			}
1287
		}
1288
 
1289
		if (strlen($selected) > 4 || $selected === 'now') {
1290
			$selected = date('Y', strtotime($selected));
1291
		} elseif ($selected === false) {
1292
			$selected = null;
1293
		}
1294
		$yearOptions = array('min' => $minYear, 'max' => $maxYear);
1295
		return $this->select(
1296
			$fieldName . ".year", $this->__generateOptions('year', $yearOptions),
1297
			$selected, $attributes, $showEmpty
1298
		);
1299
	}
1300
/**
1301
 * Returns a SELECT element for months.
1302
 *
1303
 * @param string $fieldName Prefix name for the SELECT element
1304
 * @param string $selected Option which is selected.
1305
 * @param array $attributes Attributes for the select element
1306
 *		'monthNames' is set and false 2 digit numbers will be used instead of text.
1307
 *
1308
 * @param boolean $showEmpty Show/hide the empty select option
1309
 * @return string
1310
 */
1311
	function month($fieldName, $selected = null, $attributes = array(), $showEmpty = true) {
1312
		if ((empty($selected) || $selected === true) && $value = $this->value($fieldName)) {
1313
			if (is_array($value)) {
1314
				extract($value);
1315
				$selected = $month;
1316
			} else {
1317
				if (empty($value)) {
1318
					if (!$showEmpty) {
1319
						$selected = 'now';
1320
					}
1321
				} else {
1322
					$selected = $value;
1323
				}
1324
			}
1325
		}
1326
 
1327
		if (strlen($selected) > 2) {
1328
			$selected = date('m', strtotime($selected));
1329
		} elseif ($selected === false) {
1330
			$selected = null;
1331
		}
1332
		$defaults = array('monthNames' => true);
1333
		$attributes = array_merge($defaults, (array) $attributes);
1334
		$monthNames = $attributes['monthNames'];
1335
		unset($attributes['monthNames']);
1336
 
1337
		return $this->select(
1338
			$fieldName . ".month",
1339
			$this->__generateOptions('month', array('monthNames' => $monthNames)),
1340
			$selected, $attributes, $showEmpty
1341
		);
1342
	}
1343
/**
1344
 * Returns a SELECT element for hours.
1345
 *
1346
 * @param string $fieldName Prefix name for the SELECT element
1347
 * @param boolean $format24Hours True for 24 hours format
1348
 * @param string $selected Option which is selected.
1349
 * @param array $attributes List of HTML attributes
1350
 * @param mixed $showEmpty True to show an empty element, or a string to provide default empty element text
1351
 * @return string
1352
 */
1353
	function hour($fieldName, $format24Hours = false, $selected = null, $attributes = array(), $showEmpty = true) {
1354
		if ((empty($selected) || $selected === true) && $value = $this->value($fieldName)) {
1355
			if (is_array($value)) {
1356
				extract($value);
1357
				$selected = $hour;
1358
			} else {
1359
				if (empty($value)) {
1360
					if (!$showEmpty) {
1361
						$selected = 'now';
1362
					}
1363
				} else {
1364
					$selected = $value;
1365
				}
1366
			}
1367
		} else {
1368
			$value = $selected;
1369
		}
1370
 
1371
		if (strlen($selected) > 2) {
1372
			if ($format24Hours) {
1373
				$selected = date('H', strtotime($value));
1374
			} else {
1375
				$selected = date('g', strtotime($value));
1376
			}
1377
		} elseif ($selected === false) {
1378
			$selected = null;
1379
		}
1380
		return $this->select(
1381
			$fieldName . ".hour",
1382
			$this->__generateOptions($format24Hours ? 'hour24' : 'hour'),
1383
			$selected, $attributes, $showEmpty
1384
		);
1385
	}
1386
/**
1387
 * Returns a SELECT element for minutes.
1388
 *
1389
 * @param string $fieldName Prefix name for the SELECT element
1390
 * @param string $selected Option which is selected.
1391
 * @param string $attributes Array of Attributes
1392
 * @param bool $showEmpty True to show an empty element, or a string to provide default empty element text
1393
 * @return string
1394
 */
1395
	function minute($fieldName, $selected = null, $attributes = array(), $showEmpty = true) {
1396
		if ((empty($selected) || $selected === true) && $value = $this->value($fieldName)) {
1397
			if (is_array($value)) {
1398
				extract($value);
1399
				$selected = $min;
1400
			} else {
1401
				if (empty($value)) {
1402
					if (!$showEmpty) {
1403
						$selected = 'now';
1404
					}
1405
				} else {
1406
					$selected = $value;
1407
				}
1408
			}
1409
		}
1410
 
1411
		if (strlen($selected) > 2) {
1412
			$selected = date('i', strtotime($selected));
1413
		} elseif ($selected === false) {
1414
			$selected = null;
1415
		}
1416
		$minuteOptions = array();
1417
 
1418
		if (isset($attributes['interval'])) {
1419
			$minuteOptions['interval'] = $attributes['interval'];
1420
			unset($attributes['interval']);
1421
		}
1422
		return $this->select(
1423
			$fieldName . ".min", $this->__generateOptions('minute', $minuteOptions),
1424
			$selected, $attributes, $showEmpty
1425
		);
1426
	}
1427
/**
1428
 * Returns a SELECT element for AM or PM.
1429
 *
1430
 * @param string $fieldName Prefix name for the SELECT element
1431
 * @param string $selected Option which is selected.
1432
 * @param string $attributes Array of Attributes
1433
 * @param bool $showEmpty Show/Hide an empty option
1434
 * @return string
1435
 */
1436
	function meridian($fieldName, $selected = null, $attributes = array(), $showEmpty = true) {
1437
		if ((empty($selected) || $selected === true) && $value = $this->value($fieldName)) {
1438
			if (is_array($value)) {
1439
				extract($value);
1440
				$selected = $meridian;
1441
			} else {
1442
				if (empty($value)) {
1443
					if (!$showEmpty) {
1444
						$selected = date('a');
1445
					}
1446
				} else {
1447
					$selected = date('a', strtotime($value));
1448
				}
1449
			}
1450
		}
1451
 
1452
		if ($selected === false) {
1453
			$selected = null;
1454
		}
1455
		return $this->select(
1456
			$fieldName . ".meridian", $this->__generateOptions('meridian'),
1457
			$selected, $attributes, $showEmpty
1458
		);
1459
	}
1460
/**
1461
 * Returns a set of SELECT elements for a full datetime setup: day, month and year, and then time.
1462
 *
1463
 * @param string $fieldName Prefix name for the SELECT element
1464
 * @param string $dateFormat DMY, MDY, YMD or NONE.
1465
 * @param string $timeFormat 12, 24, NONE
1466
 * @param string $selected Option which is selected.
1467
 * @param string $attributes array of Attributes
1468
 *		'monthNames' If set and false numbers will be used for month select instead of text.
1469
 *		'minYear' The lowest year to use in the year select
1470
 *		'maxYear' The maximum year to use in the year select
1471
 *		'interval' The interval for the minutes select. Defaults to 1
1472
 *		'separator' The contents of the string between select elements. Defaults to '-'
1473
 * @param bool $showEmpty Whether or not to show an empty default value.
1474
 * @return string The HTML formatted OPTION element
1475
 */
1476
	function dateTime($fieldName, $dateFormat = 'DMY', $timeFormat = '12', $selected = null, $attributes = array(), $showEmpty = true) {
1477
		$year = $month = $day = $hour = $min = $meridian = null;
1478
 
1479
		if (empty($selected)) {
1480
			$selected = $this->value($fieldName);
1481
		}
1482
 
1483
		if ($selected === null && $showEmpty != true) {
1484
			$selected = time();
1485
		}
1486
 
1487
		if (!empty($selected)) {
1488
			if (is_array($selected)) {
1489
				extract($selected);
1490
			} else {
1491
				if (is_numeric($selected)) {
1492
					$selected = strftime('%Y-%m-%d %H:%M:%S', $selected);
1493
				}
1494
				$meridian = 'am';
1495
				$pos = strpos($selected, '-');
1496
				if ($pos !== false) {
1497
					$date = explode('-', $selected);
1498
					$days = explode(' ', $date[2]);
1499
					$day = $days[0];
1500
					$month = $date[1];
1501
					$year = $date[0];
1502
				} else {
1503
					$days[1] = $selected;
1504
				}
1505
 
1506
				if ($timeFormat != 'NONE' && !empty($timeFormat)) {
1507
					$time = explode(':', $days[1]);
1508
					$check = str_replace(':', '', $days[1]);
1509
 
1510
					if (($check > 115959) && $timeFormat == '12') {
1511
						$time[0] = $time[0] - 12;
1512
						$meridian = 'pm';
1513
					} elseif ($time[0] == '00' && $timeFormat == '12') {
1514
						$time[0] = 12;
1515
					} elseif ($time[0] > 12) {
1516
						$meridian = 'pm';
1517
					}
1518
					if ($time[0] == 0 && $timeFormat == '12') {
1519
						$time[0] = 12;
1520
					}
1521
					$hour = $time[0];
1522
					$min = $time[1];
1523
				}
1524
			}
1525
		}
1526
 
1527
		$elements = array('Day','Month','Year','Hour','Minute','Meridian');
1528
		$defaults = array(
1529
			'minYear' => null, 'maxYear' => null, 'separator' => '-',
1530
			'interval' => 1, 'monthNames' => true
1531
		);
1532
		$attributes = array_merge($defaults, (array) $attributes);
1533
		if (isset($attributes['minuteInterval'])) {
1534
			$attributes['interval'] = $attributes['minuteInterval'];
1535
			unset($attributes['minuteInterval']);
1536
		}
1537
		$minYear = $attributes['minYear'];
1538
		$maxYear = $attributes['maxYear'];
1539
		$separator = $attributes['separator'];
1540
		$interval = $attributes['interval'];
1541
		$monthNames = $attributes['monthNames'];
1542
		$attributes = array_diff_key($attributes, $defaults);
1543
 
1544
		if (isset($attributes['id'])) {
1545
			if (is_string($attributes['id'])) {
1546
				// build out an array version
1547
				foreach ($elements as $element) {
1548
					$selectAttrName = 'select' . $element . 'Attr';
1549
					${$selectAttrName} = $attributes;
1550
					${$selectAttrName}['id'] = $attributes['id'] . $element;
1551
				}
1552
			} elseif (is_array($attributes['id'])) {
1553
				// check for missing ones and build selectAttr for each element
1554
				foreach ($elements as $element) {
1555
					$selectAttrName = 'select' . $element . 'Attr';
1556
					${$selectAttrName} = $attributes;
1557
					${$selectAttrName}['id'] = $attributes['id'][strtolower($element)];
1558
				}
1559
			}
1560
		} else {
1561
			// build the selectAttrName with empty id's to pass
1562
			foreach ($elements as $element) {
1563
				$selectAttrName = 'select' . $element . 'Attr';
1564
				${$selectAttrName} = $attributes;
1565
			}
1566
		}
1567
 
1568
		$opt = '';
1569
 
1570
		if ($dateFormat != 'NONE') {
1571
			$selects = array();
1572
			foreach (preg_split('//', $dateFormat, -1, PREG_SPLIT_NO_EMPTY) as $char) {
1573
				switch ($char) {
1574
					case 'Y':
1575
						$selects[] = $this->year(
1576
							$fieldName, $minYear, $maxYear, $year, $selectYearAttr, $showEmpty
1577
						);
1578
					break;
1579
					case 'M':
1580
						$selectMonthAttr['monthNames'] = $monthNames;
1581
						$selects[] = $this->month($fieldName, $month, $selectMonthAttr, $showEmpty);
1582
					break;
1583
					case 'D':
1584
						$selects[] = $this->day($fieldName, $day, $selectDayAttr, $showEmpty);
1585
					break;
1586
				}
1587
			}
1588
			$opt = implode($separator, $selects);
1589
		}
1590
 
1591
		switch ($timeFormat) {
1592
			case '24':
1593
				$selectMinuteAttr['interval'] = $interval;
1594
				$opt .= $this->hour($fieldName, true, $hour, $selectHourAttr, $showEmpty) . ':' .
1595
				$this->minute($fieldName, $min, $selectMinuteAttr, $showEmpty);
1596
			break;
1597
			case '12':
1598
				$selectMinuteAttr['interval'] = $interval;
1599
				$opt .= $this->hour($fieldName, false, $hour, $selectHourAttr, $showEmpty) . ':' .
1600
				$this->minute($fieldName, $min, $selectMinuteAttr, $showEmpty) . ' ' .
1601
				$this->meridian($fieldName, $meridian, $selectMeridianAttr, $showEmpty);
1602
			break;
1603
			case 'NONE':
1604
			default:
1605
				$opt .= '';
1606
			break;
1607
		}
1608
		return $opt;
1609
	}
1610
/**
1611
 * Gets the input field name for the current tag
1612
 *
1613
 * @param array $options
1614
 * @param string $key
1615
 * @return array
1616
 */
1617
	function __name($options = array(), $field = null, $key = 'name') {
1618
		if ($this->requestType == 'get') {
1619
			if ($options === null) {
1620
				$options = array();
1621
			} elseif (is_string($options)) {
1622
				$field = $options;
1623
				$options = 0;
1624
			}
1625
 
1626
			if (!empty($field)) {
1627
				$this->setEntity($field);
1628
			}
1629
 
1630
			if (is_array($options) && isset($options[$key])) {
1631
				return $options;
1632
			}
1633
			$name = $this->field();
1634
 
1635
			if (is_array($options)) {
1636
				$options[$key] = $name;
1637
				return $options;
1638
			} else {
1639
				return $name;
1640
			}
1641
		}
1642
		return parent::__name($options, $field, $key);
1643
	}
1644
/**
1645
 * Returns an array of formatted OPTION/OPTGROUP elements
1646
 * @access private
1647
 * @return array
1648
 */
1649
	function __selectOptions($elements = array(), $selected = null, $parents = array(), $showParents = null, $attributes = array()) {
1650
		$select = array();
1651
		$attributes = array_merge(array('escape' => true, 'style' => null), $attributes);
1652
		$selectedIsEmpty = ($selected === '' || $selected === null);
1653
		$selectedIsArray = is_array($selected);
1654
 
1655
		foreach ($elements as $name => $title) {
1656
			$htmlOptions = array();
1657
			if (is_array($title) && (!isset($title['name']) || !isset($title['value']))) {
1658
				if (!empty($name)) {
1659
					if ($attributes['style'] === 'checkbox') {
1660
						$select[] = $this->Html->tags['fieldsetend'];
1661
					} else {
1662
						$select[] = $this->Html->tags['optiongroupend'];
1663
					}
1664
					$parents[] = $name;
1665
				}
1666
				$select = array_merge($select, $this->__selectOptions(
1667
					$title, $selected, $parents, $showParents, $attributes
1668
				));
1669
 
1670
				if (!empty($name)) {
1671
					if ($attributes['style'] === 'checkbox') {
1672
						$select[] = sprintf($this->Html->tags['fieldsetstart'], $name);
1673
					} else {
1674
						$select[] = sprintf($this->Html->tags['optiongroup'], $name, '');
1675
					}
1676
				}
1677
				$name = null;
1678
			} elseif (is_array($title)) {
1679
				$htmlOptions = $title;
1680
				$name = $title['value'];
1681
				$title = $title['name'];
1682
				unset($htmlOptions['name'], $htmlOptions['value']);
1683
			}
1684
 
1685
			if ($name !== null) {
1686
				if ((!$selectedIsEmpty && $selected == $name) || ($selectedIsArray && in_array($name, $selected))) {
1687
					if ($attributes['style'] === 'checkbox') {
1688
						$htmlOptions['checked'] = true;
1689
					} else {
1690
						$htmlOptions['selected'] = 'selected';
1691
					}
1692
				}
1693
 
1694
				if ($showParents || (!in_array($title, $parents))) {
1695
					$title = ($attributes['escape']) ? h($title) : $title;
1696
 
1697
					if ($attributes['style'] === 'checkbox') {
1698
						$htmlOptions['value'] = $name;
1699
 
1700
						$tagName = Inflector::camelize(
1701
							$this->model() . '_' . $this->field().'_'.Inflector::underscore($name)
1702
						);
1703
						$htmlOptions['id'] = $tagName;
1704
						$label = array('for' => $tagName);
1705
 
1706
						if (isset($htmlOptions['checked']) && $htmlOptions['checked'] === true) {
1707
							$label['class'] = 'selected';
1708
						}
1709
 
1710
						list($name) = array_values($this->__name());
1711
 
1712
						if (empty($attributes['class'])) {
1713
							$attributes['class'] = 'checkbox';
1714
						}
1715
						$label = $this->label(null, $title, $label);
1716
						$item = sprintf(
1717
							$this->Html->tags['checkboxmultiple'], $name,
1718
							$this->_parseAttributes($htmlOptions)
1719
						);
1720
						$select[] = $this->Html->div($attributes['class'], $item . $label);
1721
					} else {
1722
						$select[] = sprintf(
1723
							$this->Html->tags['selectoption'],
1724
							$name, $this->_parseAttributes($htmlOptions), $title
1725
						);
1726
					}
1727
				}
1728
			}
1729
		}
1730
 
1731
		return array_reverse($select, true);
1732
	}
1733
/**
1734
 * Generates option lists for common <select /> menus
1735
 * @access private
1736
 */
1737
	function __generateOptions($name, $options = array()) {
1738
		if (!empty($this->options[$name])) {
1739
			return $this->options[$name];
1740
		}
1741
		$data = array();
1742
 
1743
		switch ($name) {
1744
			case 'minute':
1745
				if (isset($options['interval'])) {
1746
					$interval = $options['interval'];
1747
				} else {
1748
					$interval = 1;
1749
				}
1750
				$i = 0;
1751
				while ($i < 60) {
1752
					$data[$i] = sprintf('%02d', $i);
1753
					$i += $interval;
1754
				}
1755
			break;
1756
			case 'hour':
1757
				for ($i = 1; $i <= 12; $i++) {
1758
					$data[sprintf('%02d', $i)] = $i;
1759
				}
1760
			break;
1761
			case 'hour24':
1762
				for ($i = 0; $i <= 23; $i++) {
1763
					$data[sprintf('%02d', $i)] = $i;
1764
				}
1765
			break;
1766
			case 'meridian':
1767
				$data = array('am' => 'am', 'pm' => 'pm');
1768
			break;
1769
			case 'day':
1770
				$min = 1;
1771
				$max = 31;
1772
 
1773
				if (isset($options['min'])) {
1774
					$min = $options['min'];
1775
				}
1776
				if (isset($options['max'])) {
1777
					$max = $options['max'];
1778
				}
1779
 
1780
				for ($i = $min; $i <= $max; $i++) {
1781
					$data[sprintf('%02d', $i)] = $i;
1782
				}
1783
			break;
1784
			case 'month':
1785
				if ($options['monthNames']) {
1786
					$data['01'] = __('January', true);
1787
					$data['02'] = __('February', true);
1788
					$data['03'] = __('March', true);
1789
					$data['04'] = __('April', true);
1790
					$data['05'] = __('May', true);
1791
					$data['06'] = __('June', true);
1792
					$data['07'] = __('July', true);
1793
					$data['08'] = __('August', true);
1794
					$data['09'] = __('September', true);
1795
					$data['10'] = __('October', true);
1796
					$data['11'] = __('November', true);
1797
					$data['12'] = __('December', true);
1798
				} else {
1799
					for ($m = 1; $m <= 12; $m++) {
1800
						$data[sprintf("%02s", $m)] = strftime("%m", mktime(1, 1, 1, $m, 1, 1999));
1801
					}
1802
				}
1803
			break;
1804
			case 'year':
1805
				$current = intval(date('Y'));
1806
 
1807
				if (!isset($options['min'])) {
1808
					$min = $current - 20;
1809
				} else {
1810
					$min = $options['min'];
1811
				}
1812
 
1813
				if (!isset($options['max'])) {
1814
					$max = $current + 20;
1815
				} else {
1816
					$max = $options['max'];
1817
				}
1818
				if ($min > $max) {
1819
					list($min, $max) = array($max, $min);
1820
				}
1821
				for ($i = $min; $i <= $max; $i++) {
1822
					$data[$i] = $i;
1823
				}
1824
				$data = array_reverse($data, true);
1825
			break;
1826
		}
1827
		$this->__options[$name] = $data;
1828
		return $this->__options[$name];
1829
	}
1830
/**
1831
 * Sets field defaults and adds field to form security input hash
1832
 *
1833
 * @param string $field
1834
 * @param array $options
1835
 * @return array
1836
 * @access protected
1837
 */
1838
	function _initInputField($field, $options = array()) {
1839
		if (isset($options['secure'])) {
1840
			$secure = $options['secure'];
1841
			unset($options['secure']);
1842
		} else {
1843
			$secure = (isset($this->params['_Token']) && !empty($this->params['_Token']));
1844
		}
1845
		$result = parent::_initInputField($field, $options);
1846
 
1847
		if ($secure) {
1848
			$this->__secure();
1849
		}
1850
		return $result;
1851
	}
1852
}
1853
 
1854
?>