Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
13532 anikendra 1
<?php
2
/**
3
 * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
4
 * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
5
 *
6
 * Licensed under The MIT License
7
 * For full copyright and license information, please see the LICENSE.txt
8
 * Redistributions of files must retain the above copyright notice.
9
 *
10
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
11
 * @link          http://cakephp.org CakePHP(tm) Project
12
 * @package       Cake.View.Helper
13
 * @since         CakePHP(tm) v 0.10.0.1076
14
 * @license       http://www.opensource.org/licenses/mit-license.php MIT License
15
 */
16
 
17
App::uses('ClassRegistry', 'Utility');
18
App::uses('AppHelper', 'View/Helper');
19
App::uses('Hash', 'Utility');
20
 
21
/**
22
 * Form helper library.
23
 *
24
 * Automatic generation of HTML FORMs from given data.
25
 *
26
 * @package       Cake.View.Helper
27
 * @property      HtmlHelper $Html
28
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html
29
 */
30
class FormHelper extends AppHelper {
31
 
32
/**
33
 * Other helpers used by FormHelper
34
 *
35
 * @var array
36
 */
37
	public $helpers = array('Html');
38
 
39
/**
40
 * Options used by DateTime fields
41
 *
42
 * @var array
43
 */
44
	protected $_options = array(
45
		'day' => array(), 'minute' => array(), 'hour' => array(),
46
		'month' => array(), 'year' => array(), 'meridian' => array()
47
	);
48
 
49
/**
50
 * List of fields created, used with secure forms.
51
 *
52
 * @var array
53
 */
54
	public $fields = array();
55
 
56
/**
57
 * Constant used internally to skip the securing process,
58
 * and neither add the field to the hash or to the unlocked fields.
59
 *
60
 * @var string
61
 */
62
	const SECURE_SKIP = 'skip';
63
 
64
/**
65
 * Defines the type of form being created. Set by FormHelper::create().
66
 *
67
 * @var string
68
 */
69
	public $requestType = null;
70
 
71
/**
72
 * The default model being used for the current form.
73
 *
74
 * @var string
75
 */
76
	public $defaultModel = null;
77
 
78
/**
79
 * Persistent default options used by input(). Set by FormHelper::create().
80
 *
81
 * @var array
82
 */
83
	protected $_inputDefaults = array();
84
 
85
/**
86
 * An array of field names that have been excluded from
87
 * the Token hash used by SecurityComponent's validatePost method
88
 *
89
 * @see FormHelper::_secure()
90
 * @see SecurityComponent::validatePost()
91
 * @var array
92
 */
93
	protected $_unlockedFields = array();
94
 
95
/**
96
 * Holds the model references already loaded by this helper
97
 * product of trying to inspect them out of field names
98
 *
99
 * @var array
100
 */
101
	protected $_models = array();
102
 
103
/**
104
 * Holds all the validation errors for models loaded and inspected
105
 * it can also be set manually to be able to display custom error messages
106
 * in the any of the input fields generated by this helper
107
 *
108
 * @var array
109
 */
110
	public $validationErrors = array();
111
 
112
/**
113
 * Copies the validationErrors variable from the View object into this instance
114
 *
115
 * @param View $View The View this helper is being attached to.
116
 * @param array $settings Configuration settings for the helper.
117
 */
118
	public function __construct(View $View, $settings = array()) {
119
		parent::__construct($View, $settings);
120
		$this->validationErrors =& $View->validationErrors;
121
	}
122
 
123
/**
124
 * Guess the location for a model based on its name and tries to create a new instance
125
 * or get an already created instance of the model
126
 *
127
 * @param string $model
128
 * @return Model model instance
129
 */
130
	protected function _getModel($model) {
131
		$object = null;
132
		if (!$model || $model === 'Model') {
133
			return $object;
134
		}
135
 
136
		if (array_key_exists($model, $this->_models)) {
137
			return $this->_models[$model];
138
		}
139
 
140
		if (ClassRegistry::isKeySet($model)) {
141
			$object = ClassRegistry::getObject($model);
142
		} elseif (isset($this->request->params['models'][$model])) {
143
			$plugin = $this->request->params['models'][$model]['plugin'];
144
			$plugin .= ($plugin) ? '.' : null;
145
			$object = ClassRegistry::init(array(
146
				'class' => $plugin . $this->request->params['models'][$model]['className'],
147
				'alias' => $model
148
			));
149
		} elseif (ClassRegistry::isKeySet($this->defaultModel)) {
150
			$defaultObject = ClassRegistry::getObject($this->defaultModel);
151
			if ($defaultObject && in_array($model, array_keys($defaultObject->getAssociated()), true) && isset($defaultObject->{$model})) {
152
				$object = $defaultObject->{$model};
153
			}
154
		} else {
155
			$object = ClassRegistry::init($model, true);
156
		}
157
 
158
		$this->_models[$model] = $object;
159
		if (!$object) {
160
			return null;
161
		}
162
 
163
		$this->fieldset[$model] = array('fields' => null, 'key' => $object->primaryKey, 'validates' => null);
164
		return $object;
165
	}
166
 
167
/**
168
 * Inspects the model properties to extract information from them.
169
 * Currently it can extract information from the the fields, the primary key and required fields
170
 *
171
 * The $key parameter accepts the following list of values:
172
 *
173
 * - key: Returns the name of the primary key for the model
174
 * - fields: Returns the model schema
175
 * - validates: returns the list of fields that are required
176
 * - errors: returns the list of validation errors
177
 *
178
 * If the $field parameter is passed if will return the information for that sole field.
179
 *
180
 * `$this->_introspectModel('Post', 'fields', 'title');` will return the schema information for title column
181
 *
182
 * @param string $model name of the model to extract information from
183
 * @param string $key name of the special information key to obtain (key, fields, validates, errors)
184
 * @param string $field name of the model field to get information from
185
 * @return mixed information extracted for the special key and field in a model
186
 */
187
	protected function _introspectModel($model, $key, $field = null) {
188
		$object = $this->_getModel($model);
189
		if (!$object) {
190
			return;
191
		}
192
 
193
		if ($key === 'key') {
194
			return $this->fieldset[$model]['key'] = $object->primaryKey;
195
		}
196
 
197
		if ($key === 'fields') {
198
			if (!isset($this->fieldset[$model]['fields'])) {
199
				$this->fieldset[$model]['fields'] = $object->schema();
200
				foreach ($object->hasAndBelongsToMany as $alias => $assocData) {
201
					$this->fieldset[$object->alias]['fields'][$alias] = array('type' => 'multiple');
202
				}
203
			}
204
			if ($field === null || $field === false) {
205
				return $this->fieldset[$model]['fields'];
206
			} elseif (isset($this->fieldset[$model]['fields'][$field])) {
207
				return $this->fieldset[$model]['fields'][$field];
208
			}
209
			return isset($object->hasAndBelongsToMany[$field]) ? array('type' => 'multiple') : null;
210
		}
211
 
212
		if ($key === 'errors' && !isset($this->validationErrors[$model])) {
213
			$this->validationErrors[$model] =& $object->validationErrors;
214
			return $this->validationErrors[$model];
215
		} elseif ($key === 'errors' && isset($this->validationErrors[$model])) {
216
			return $this->validationErrors[$model];
217
		}
218
 
219
		if ($key === 'validates' && !isset($this->fieldset[$model]['validates'])) {
220
			$validates = array();
221
			foreach ($object->validator() as $validateField => $validateProperties) {
222
				if ($this->_isRequiredField($validateProperties)) {
223
					$validates[$validateField] = true;
224
				}
225
			}
226
			$this->fieldset[$model]['validates'] = $validates;
227
		}
228
 
229
		if ($key === 'validates') {
230
			if (empty($field)) {
231
				return $this->fieldset[$model]['validates'];
232
			}
233
			return isset($this->fieldset[$model]['validates'][$field]) ?
234
				$this->fieldset[$model]['validates'] : null;
235
		}
236
	}
237
 
238
/**
239
 * Returns if a field is required to be filled based on validation properties from the validating object.
240
 *
241
 * @param CakeValidationSet $validationRules
242
 * @return boolean true if field is required to be filled, false otherwise
243
 */
244
	protected function _isRequiredField($validationRules) {
245
		if (empty($validationRules) || count($validationRules) === 0) {
246
			return false;
247
		}
248
 
249
		$isUpdate = $this->requestType === 'put';
250
		foreach ($validationRules as $rule) {
251
			$rule->isUpdate($isUpdate);
252
			if ($rule->skip()) {
253
				continue;
254
			}
255
 
256
			return !$rule->allowEmpty;
257
		}
258
		return false;
259
	}
260
 
261
/**
262
 * Returns false if given form field described by the current entity has no errors.
263
 * Otherwise it returns the validation message
264
 *
265
 * @return mixed Either false when there are no errors, or an array of error
266
 *    strings. An error string could be ''.
267
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::tagIsInvalid
268
 */
269
	public function tagIsInvalid() {
270
		$entity = $this->entity();
271
		$model = array_shift($entity);
272
 
273
		// 0.Model.field. Fudge entity path
274
		if (empty($model) || is_numeric($model)) {
275
			array_splice($entity, 1, 0, $model);
276
			$model = array_shift($entity);
277
		}
278
 
279
		$errors = array();
280
		if (!empty($entity) && isset($this->validationErrors[$model])) {
281
			$errors = $this->validationErrors[$model];
282
		}
283
		if (!empty($entity) && empty($errors)) {
284
			$errors = $this->_introspectModel($model, 'errors');
285
		}
286
		if (empty($errors)) {
287
			return false;
288
		}
289
		$errors = Hash::get($errors, implode('.', $entity));
290
		return $errors === null ? false : $errors;
291
	}
292
 
293
/**
294
 * Returns an HTML FORM element.
295
 *
296
 * ### Options:
297
 *
298
 * - `type` Form method defaults to POST
299
 * - `action`  The controller action the form submits to, (optional).
300
 * - `url`  The URL the form submits to. Can be a string or a URL array. If you use 'url'
301
 *    you should leave 'action' undefined.
302
 * - `default`  Allows for the creation of Ajax forms. Set this to false to prevent the default event handler.
303
 *   Will create an onsubmit attribute if it doesn't not exist. If it does, default action suppression
304
 *   will be appended.
305
 * - `onsubmit` Used in conjunction with 'default' to create ajax forms.
306
 * - `inputDefaults` set the default $options for FormHelper::input(). Any options that would
307
 *   be set when using FormHelper::input() can be set here. Options set with `inputDefaults`
308
 *   can be overridden when calling input()
309
 * - `encoding` Set the accept-charset encoding for the form. Defaults to `Configure::read('App.encoding')`
310
 *
311
 * @param mixed $model The model name for which the form is being defined. Should
312
 *   include the plugin name for plugin models. e.g. `ContactManager.Contact`.
313
 *   If an array is passed and $options argument is empty, the array will be used as options.
314
 *   If `false` no model is used.
315
 * @param array $options An array of html attributes and options.
316
 * @return string An formatted opening FORM tag.
317
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#options-for-create
318
 */
319
	public function create($model = null, $options = array()) {
320
		$created = $id = false;
321
		$append = '';
322
 
323
		if (is_array($model) && empty($options)) {
324
			$options = $model;
325
			$model = null;
326
		}
327
 
328
		if (empty($model) && $model !== false && !empty($this->request->params['models'])) {
329
			$model = key($this->request->params['models']);
330
		} elseif (empty($model) && empty($this->request->params['models'])) {
331
			$model = false;
332
		}
333
		$this->defaultModel = $model;
334
 
335
		$key = null;
336
		if ($model !== false) {
337
			list($plugin, $model) = pluginSplit($model, true);
338
			$key = $this->_introspectModel($plugin . $model, 'key');
339
			$this->setEntity($model, true);
340
		}
341
 
342
		if ($model !== false && $key) {
343
			$recordExists = (
344
				isset($this->request->data[$model]) &&
345
				!empty($this->request->data[$model][$key]) &&
346
				!is_array($this->request->data[$model][$key])
347
			);
348
 
349
			if ($recordExists) {
350
				$created = true;
351
				$id = $this->request->data[$model][$key];
352
			}
353
		}
354
 
355
		$options = array_merge(array(
356
			'type' => ($created && empty($options['action'])) ? 'put' : 'post',
357
			'action' => null,
358
			'url' => null,
359
			'default' => true,
360
			'encoding' => strtolower(Configure::read('App.encoding')),
361
			'inputDefaults' => array()),
362
		$options);
363
		$this->inputDefaults($options['inputDefaults']);
364
		unset($options['inputDefaults']);
365
 
366
		if (!isset($options['id'])) {
367
			$domId = isset($options['action']) ? $options['action'] : $this->request['action'];
368
			$options['id'] = $this->domId($domId . 'Form');
369
		}
370
 
371
		if ($options['action'] === null && $options['url'] === null) {
372
			$options['action'] = $this->request->here(false);
373
		} elseif (empty($options['url']) || is_array($options['url'])) {
374
			if (empty($options['url']['controller'])) {
375
				if (!empty($model)) {
376
					$options['url']['controller'] = Inflector::underscore(Inflector::pluralize($model));
377
				} elseif (!empty($this->request->params['controller'])) {
378
					$options['url']['controller'] = Inflector::underscore($this->request->params['controller']);
379
				}
380
			}
381
			if (empty($options['action'])) {
382
				$options['action'] = $this->request->params['action'];
383
			}
384
 
385
			$plugin = null;
386
			if ($this->plugin) {
387
				$plugin = Inflector::underscore($this->plugin);
388
			}
389
			$actionDefaults = array(
390
				'plugin' => $plugin,
391
				'controller' => $this->_View->viewPath,
392
				'action' => $options['action'],
393
			);
394
			$options['action'] = array_merge($actionDefaults, (array)$options['url']);
395
			if (empty($options['action'][0]) && !empty($id)) {
396
				$options['action'][0] = $id;
397
			}
398
		} elseif (is_string($options['url'])) {
399
			$options['action'] = $options['url'];
400
		}
401
		unset($options['url']);
402
 
403
		switch (strtolower($options['type'])) {
404
			case 'get':
405
				$htmlAttributes['method'] = 'get';
406
				break;
407
			case 'file':
408
				$htmlAttributes['enctype'] = 'multipart/form-data';
409
				$options['type'] = ($created) ? 'put' : 'post';
410
			case 'post':
411
			case 'put':
412
			case 'delete':
413
				$append .= $this->hidden('_method', array(
414
					'name' => '_method', 'value' => strtoupper($options['type']), 'id' => null,
415
					'secure' => self::SECURE_SKIP
416
				));
417
			default:
418
				$htmlAttributes['method'] = 'post';
419
		}
420
		$this->requestType = strtolower($options['type']);
421
 
422
		$action = $this->url($options['action']);
423
		unset($options['type'], $options['action']);
424
 
425
		if (!$options['default']) {
426
			if (!isset($options['onsubmit'])) {
427
				$options['onsubmit'] = '';
428
			}
429
			$htmlAttributes['onsubmit'] = $options['onsubmit'] . 'event.returnValue = false; return false;';
430
		}
431
		unset($options['default']);
432
 
433
		if (!empty($options['encoding'])) {
434
			$htmlAttributes['accept-charset'] = $options['encoding'];
435
			unset($options['encoding']);
436
		}
437
 
438
		$htmlAttributes = array_merge($options, $htmlAttributes);
439
 
440
		$this->fields = array();
441
		if ($this->requestType !== 'get') {
442
			$append .= $this->_csrfField();
443
		}
444
 
445
		if (!empty($append)) {
446
			$append = $this->Html->useTag('hiddenblock', $append);
447
		}
448
 
449
		if ($model !== false) {
450
			$this->setEntity($model, true);
451
			$this->_introspectModel($model, 'fields');
452
		}
453
		return $this->Html->useTag('form', $action, $htmlAttributes) . $append;
454
	}
455
 
456
/**
457
 * Return a CSRF input if the _Token is present.
458
 * Used to secure forms in conjunction with SecurityComponent
459
 *
460
 * @return string
461
 */
462
	protected function _csrfField() {
463
		if (empty($this->request->params['_Token'])) {
464
			return '';
465
		}
466
		if (!empty($this->request['_Token']['unlockedFields'])) {
467
			foreach ((array)$this->request['_Token']['unlockedFields'] as $unlocked) {
468
				$this->_unlockedFields[] = $unlocked;
469
			}
470
		}
471
		return $this->hidden('_Token.key', array(
472
			'value' => $this->request->params['_Token']['key'], 'id' => 'Token' . mt_rand(),
473
			'secure' => self::SECURE_SKIP
474
		));
475
	}
476
 
477
/**
478
 * Closes an HTML form, cleans up values set by FormHelper::create(), and writes hidden
479
 * input fields where appropriate.
480
 *
481
 * If $options is set a form submit button will be created. Options can be either a string or an array.
482
 *
483
 * {{{
484
 * array usage:
485
 *
486
 * array('label' => 'save'); value="save"
487
 * array('label' => 'save', 'name' => 'Whatever'); value="save" name="Whatever"
488
 * array('name' => 'Whatever'); value="Submit" name="Whatever"
489
 * array('label' => 'save', 'name' => 'Whatever', 'div' => 'good') <div class="good"> value="save" name="Whatever"
490
 * array('label' => 'save', 'name' => 'Whatever', 'div' => array('class' => 'good')); <div class="good"> value="save" name="Whatever"
491
 * }}}
492
 *
493
 * @param string|array $options as a string will use $options as the value of button,
494
 * @return string a closing FORM tag optional submit button.
495
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#closing-the-form
496
 */
497
	public function end($options = null) {
498
		$out = null;
499
		$submit = null;
500
 
501
		if ($options !== null) {
502
			$submitOptions = array();
503
			if (is_string($options)) {
504
				$submit = $options;
505
			} else {
506
				if (isset($options['label'])) {
507
					$submit = $options['label'];
508
					unset($options['label']);
509
				}
510
				$submitOptions = $options;
511
			}
512
			$out .= $this->submit($submit, $submitOptions);
513
		}
514
		if (
515
			$this->requestType !== 'get' &&
516
			isset($this->request['_Token']) &&
517
			!empty($this->request['_Token'])
518
		) {
519
			$out .= $this->secure($this->fields);
520
			$this->fields = array();
521
		}
522
		$this->setEntity(null);
523
		$out .= $this->Html->useTag('formend');
524
 
525
		$this->_View->modelScope = false;
526
		$this->requestType = null;
527
		return $out;
528
	}
529
 
530
/**
531
 * Generates a hidden field with a security hash based on the fields used in the form.
532
 *
533
 * @param array $fields The list of fields to use when generating the hash
534
 * @return string A hidden input field with a security hash
535
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::secure
536
 */
537
	public function secure($fields = array()) {
538
		if (!isset($this->request['_Token']) || empty($this->request['_Token'])) {
539
			return;
540
		}
541
		$locked = array();
542
		$unlockedFields = $this->_unlockedFields;
543
 
544
		foreach ($fields as $key => $value) {
545
			if (!is_int($key)) {
546
				$locked[$key] = $value;
547
				unset($fields[$key]);
548
			}
549
		}
550
 
551
		sort($unlockedFields, SORT_STRING);
552
		sort($fields, SORT_STRING);
553
		ksort($locked, SORT_STRING);
554
		$fields += $locked;
555
 
556
		$locked = implode(array_keys($locked), '|');
557
		$unlocked = implode($unlockedFields, '|');
558
		$fields = Security::hash(serialize($fields) . $unlocked . Configure::read('Security.salt'), 'sha1');
559
 
560
		$out = $this->hidden('_Token.fields', array(
561
			'value' => urlencode($fields . ':' . $locked),
562
			'id' => 'TokenFields' . mt_rand()
563
		));
564
		$out .= $this->hidden('_Token.unlocked', array(
565
			'value' => urlencode($unlocked),
566
			'id' => 'TokenUnlocked' . mt_rand()
567
		));
568
		return $this->Html->useTag('hiddenblock', $out);
569
	}
570
 
571
/**
572
 * Add to or get the list of fields that are currently unlocked.
573
 * Unlocked fields are not included in the field hash used by SecurityComponent
574
 * unlocking a field once its been added to the list of secured fields will remove
575
 * it from the list of fields.
576
 *
577
 * @param string $name The dot separated name for the field.
578
 * @return mixed Either null, or the list of fields.
579
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::unlockField
580
 */
581
	public function unlockField($name = null) {
582
		if ($name === null) {
583
			return $this->_unlockedFields;
584
		}
585
		if (!in_array($name, $this->_unlockedFields)) {
586
			$this->_unlockedFields[] = $name;
587
		}
588
		$index = array_search($name, $this->fields);
589
		if ($index !== false) {
590
			unset($this->fields[$index]);
591
		}
592
		unset($this->fields[$name]);
593
	}
594
 
595
/**
596
 * Determine which fields of a form should be used for hash.
597
 * Populates $this->fields
598
 *
599
 * @param boolean $lock Whether this field should be part of the validation
600
 *     or excluded as part of the unlockedFields.
601
 * @param string $field Reference to field to be secured. Should be dot separated to indicate nesting.
602
 * @param mixed $value Field value, if value should not be tampered with.
603
 * @return mixed|null Not used yet
604
 */
605
	protected function _secure($lock, $field = null, $value = null) {
606
		if (!$field) {
607
			$field = $this->entity();
608
		} elseif (is_string($field)) {
609
			$field = Hash::filter(explode('.', $field));
610
		}
611
 
612
		foreach ($this->_unlockedFields as $unlockField) {
613
			$unlockParts = explode('.', $unlockField);
614
			if (array_values(array_intersect($field, $unlockParts)) === $unlockParts) {
615
				return;
616
			}
617
		}
618
 
619
		$field = implode('.', $field);
620
		$field = preg_replace('/(\.\d+)+$/', '', $field);
621
 
622
		if ($lock) {
623
			if (!in_array($field, $this->fields)) {
624
				if ($value !== null) {
625
					return $this->fields[$field] = $value;
626
				}
627
				$this->fields[] = $field;
628
			}
629
		} else {
630
			$this->unlockField($field);
631
		}
632
	}
633
 
634
/**
635
 * Returns true if there is an error for the given field, otherwise false
636
 *
637
 * @param string $field This should be "Modelname.fieldname"
638
 * @return boolean If there are errors this method returns true, else false.
639
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::isFieldError
640
 */
641
	public function isFieldError($field) {
642
		$this->setEntity($field);
643
		return (bool)$this->tagIsInvalid();
644
	}
645
 
646
/**
647
 * Returns a formatted error message for given FORM field, NULL if no errors.
648
 *
649
 * ### Options:
650
 *
651
 * - `escape` boolean - Whether or not to html escape the contents of the error.
652
 * - `wrap` mixed - Whether or not the error message should be wrapped in a div. If a
653
 *   string, will be used as the HTML tag to use.
654
 * - `class` string - The class name for the error message
655
 *
656
 * @param string $field A field name, like "Modelname.fieldname"
657
 * @param string|array $text Error message as string or array of messages.
658
 *   If array contains `attributes` key it will be used as options for error container
659
 * @param array $options Rendering options for <div /> wrapper tag
660
 * @return string If there are errors this method returns an error message, otherwise null.
661
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::error
662
 */
663
	public function error($field, $text = null, $options = array()) {
664
		$defaults = array('wrap' => true, 'class' => 'error-message', 'escape' => true);
665
		$options = array_merge($defaults, $options);
666
		$this->setEntity($field);
667
 
668
		$error = $this->tagIsInvalid();
669
		if ($error === false) {
670
			return null;
671
		}
672
		if (is_array($text)) {
673
			if (isset($text['attributes']) && is_array($text['attributes'])) {
674
				$options = array_merge($options, $text['attributes']);
675
				unset($text['attributes']);
676
			}
677
			$tmp = array();
678
			foreach ($error as &$e) {
679
				if (isset($text[$e])) {
680
					$tmp[] = $text[$e];
681
				} else {
682
					$tmp[] = $e;
683
				}
684
			}
685
			$text = $tmp;
686
		}
687
 
688
		if ($text !== null) {
689
			$error = $text;
690
		}
691
		if (is_array($error)) {
692
			foreach ($error as &$e) {
693
				if (is_numeric($e)) {
694
					$e = __d('cake', 'Error in field %s', Inflector::humanize($this->field()));
695
				}
696
			}
697
		}
698
		if ($options['escape']) {
699
			$error = h($error);
700
			unset($options['escape']);
701
		}
702
		if (is_array($error)) {
703
			if (count($error) > 1) {
704
				$listParams = array();
705
				if (isset($options['listOptions'])) {
706
					if (is_string($options['listOptions'])) {
707
						$listParams[] = $options['listOptions'];
708
					} else {
709
						if (isset($options['listOptions']['itemOptions'])) {
710
							$listParams[] = $options['listOptions']['itemOptions'];
711
							unset($options['listOptions']['itemOptions']);
712
						} else {
713
							$listParams[] = array();
714
						}
715
						if (isset($options['listOptions']['tag'])) {
716
							$listParams[] = $options['listOptions']['tag'];
717
							unset($options['listOptions']['tag']);
718
						}
719
						array_unshift($listParams, $options['listOptions']);
720
					}
721
					unset($options['listOptions']);
722
				}
723
				array_unshift($listParams, $error);
724
				$error = call_user_func_array(array($this->Html, 'nestedList'), $listParams);
725
			} else {
726
				$error = array_pop($error);
727
			}
728
		}
729
		if ($options['wrap']) {
730
			$tag = is_string($options['wrap']) ? $options['wrap'] : 'div';
731
			unset($options['wrap']);
732
			return $this->Html->tag($tag, $error, $options);
733
		}
734
		return $error;
735
	}
736
 
737
/**
738
 * Returns a formatted LABEL element for HTML FORMs. Will automatically generate
739
 * a `for` attribute if one is not provided.
740
 *
741
 * ### Options
742
 *
743
 * - `for` - Set the for attribute, if its not defined the for attribute
744
 *   will be generated from the $fieldName parameter using
745
 *   FormHelper::domId().
746
 *
747
 * Examples:
748
 *
749
 * The text and for attribute are generated off of the fieldname
750
 *
751
 * {{{
752
 * echo $this->Form->label('Post.published');
753
 * <label for="PostPublished">Published</label>
754
 * }}}
755
 *
756
 * Custom text:
757
 *
758
 * {{{
759
 * echo $this->Form->label('Post.published', 'Publish');
760
 * <label for="PostPublished">Publish</label>
761
 * }}}
762
 *
763
 * Custom class name:
764
 *
765
 * {{{
766
 * echo $this->Form->label('Post.published', 'Publish', 'required');
767
 * <label for="PostPublished" class="required">Publish</label>
768
 * }}}
769
 *
770
 * Custom attributes:
771
 *
772
 * {{{
773
 * echo $this->Form->label('Post.published', 'Publish', array(
774
 *		'for' => 'post-publish'
775
 * ));
776
 * <label for="post-publish">Publish</label>
777
 * }}}
778
 *
779
 * @param string $fieldName This should be "Modelname.fieldname"
780
 * @param string $text Text that will appear in the label field. If
781
 *   $text is left undefined the text will be inflected from the
782
 *   fieldName.
783
 * @param array|string $options An array of HTML attributes, or a string, to be used as a class name.
784
 * @return string The formatted LABEL element
785
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::label
786
 */
787
	public function label($fieldName = null, $text = null, $options = array()) {
788
		if ($fieldName === null) {
789
			$fieldName = implode('.', $this->entity());
790
		}
791
 
792
		if ($text === null) {
793
			if (strpos($fieldName, '.') !== false) {
794
				$fieldElements = explode('.', $fieldName);
795
				$text = array_pop($fieldElements);
796
			} else {
797
				$text = $fieldName;
798
			}
799
			if (substr($text, -3) === '_id') {
800
				$text = substr($text, 0, -3);
801
			}
802
			$text = __(Inflector::humanize(Inflector::underscore($text)));
803
		}
804
 
805
		if (is_string($options)) {
806
			$options = array('class' => $options);
807
		}
808
 
809
		if (isset($options['for'])) {
810
			$labelFor = $options['for'];
811
			unset($options['for']);
812
		} else {
813
			$labelFor = $this->domId($fieldName);
814
		}
815
 
816
		return $this->Html->useTag('label', $labelFor, $options, $text);
817
	}
818
 
819
/**
820
 * Generate a set of inputs for `$fields`. If $fields is null the fields of current model
821
 * will be used.
822
 *
823
 * You can customize individual inputs through `$fields`.
824
 * {{{
825
 *	$this->Form->inputs(array(
826
 *		'name' => array('label' => 'custom label')
827
 *	));
828
 * }}}
829
 *
830
 * In addition to controller fields output, `$fields` can be used to control legend
831
 * and fieldset rendering.
832
 * `$this->Form->inputs('My legend');` Would generate an input set with a custom legend.
833
 * Passing `fieldset` and `legend` key in `$fields` array has been deprecated since 2.3,
834
 * for more fine grained control use the `fieldset` and `legend` keys in `$options` param.
835
 *
836
 * @param array $fields An array of fields to generate inputs for, or null.
837
 * @param array $blacklist A simple array of fields to not create inputs for.
838
 * @param array $options Options array. Valid keys are:
839
 * - `fieldset` Set to false to disable the fieldset. If a string is supplied it will be used as
840
 *    the class name for the fieldset element.
841
 * - `legend` Set to false to disable the legend for the generated input set. Or supply a string
842
 *    to customize the legend text.
843
 * @return string Completed form inputs.
844
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::inputs
845
 */
846
	public function inputs($fields = null, $blacklist = null, $options = array()) {
847
		$fieldset = $legend = true;
848
		$modelFields = array();
849
		$model = $this->model();
850
		if ($model) {
851
			$modelFields = array_keys((array)$this->_introspectModel($model, 'fields'));
852
		}
853
		if (is_array($fields)) {
854
			if (array_key_exists('legend', $fields) && !in_array('legend', $modelFields)) {
855
				$legend = $fields['legend'];
856
				unset($fields['legend']);
857
			}
858
 
859
			if (isset($fields['fieldset']) && !in_array('fieldset', $modelFields)) {
860
				$fieldset = $fields['fieldset'];
861
				unset($fields['fieldset']);
862
			}
863
		} elseif ($fields !== null) {
864
			$fieldset = $legend = $fields;
865
			if (!is_bool($fieldset)) {
866
				$fieldset = true;
867
			}
868
			$fields = array();
869
		}
870
 
871
		if (isset($options['legend'])) {
872
			$legend = $options['legend'];
873
		}
874
		if (isset($options['fieldset'])) {
875
			$fieldset = $options['fieldset'];
876
		}
877
 
878
		if (empty($fields)) {
879
			$fields = $modelFields;
880
		}
881
 
882
		if ($legend === true) {
883
			$actionName = __d('cake', 'New %s');
884
			$isEdit = (
885
				strpos($this->request->params['action'], 'update') !== false ||
886
				strpos($this->request->params['action'], 'edit') !== false
887
			);
888
			if ($isEdit) {
889
				$actionName = __d('cake', 'Edit %s');
890
			}
891
			$modelName = Inflector::humanize(Inflector::underscore($model));
892
			$legend = sprintf($actionName, __($modelName));
893
		}
894
 
895
		$out = null;
896
		foreach ($fields as $name => $options) {
897
			if (is_numeric($name) && !is_array($options)) {
898
				$name = $options;
899
				$options = array();
900
			}
901
			$entity = explode('.', $name);
902
			$blacklisted = (
903
				is_array($blacklist) &&
904
				(in_array($name, $blacklist) || in_array(end($entity), $blacklist))
905
			);
906
			if ($blacklisted) {
907
				continue;
908
			}
909
			$out .= $this->input($name, $options);
910
		}
911
 
912
		if (is_string($fieldset)) {
913
			$fieldsetClass = sprintf(' class="%s"', $fieldset);
914
		} else {
915
			$fieldsetClass = '';
916
		}
917
 
918
		if ($fieldset) {
919
			if ($legend) {
920
				$out = $this->Html->useTag('legend', $legend) . $out;
921
			}
922
			$out = $this->Html->useTag('fieldset', $fieldsetClass, $out);
923
		}
924
		return $out;
925
	}
926
 
927
/**
928
 * Generates a form input element complete with label and wrapper div
929
 *
930
 * ### Options
931
 *
932
 * See each field type method for more information. Any options that are part of
933
 * $attributes or $options for the different **type** methods can be included in `$options` for input().i
934
 * Additionally, any unknown keys that are not in the list below, or part of the selected type's options
935
 * will be treated as a regular html attribute for the generated input.
936
 *
937
 * - `type` - Force the type of widget you want. e.g. `type => 'select'`
938
 * - `label` - Either a string label, or an array of options for the label. See FormHelper::label().
939
 * - `div` - Either `false` to disable the div, or an array of options for the div.
940
 *	See HtmlHelper::div() for more options.
941
 * - `options` - For widgets that take options e.g. radio, select.
942
 * - `error` - Control the error message that is produced. Set to `false` to disable any kind of error reporting (field
943
 *    error and error messages).
944
 * - `errorMessage` - Boolean to control rendering error messages (field error will still occur).
945
 * - `empty` - String or boolean to enable empty select box options.
946
 * - `before` - Content to place before the label + input.
947
 * - `after` - Content to place after the label + input.
948
 * - `between` - Content to place between the label + input.
949
 * - `format` - Format template for element order. Any element that is not in the array, will not be in the output.
950
 *	- Default input format order: array('before', 'label', 'between', 'input', 'after', 'error')
951
 *	- Default checkbox format order: array('before', 'input', 'between', 'label', 'after', 'error')
952
 *	- Hidden input will not be formatted
953
 *	- Radio buttons cannot have the order of input and label elements controlled with these settings.
954
 *
955
 * @param string $fieldName This should be "Modelname.fieldname"
956
 * @param array $options Each type of input takes different options.
957
 * @return string Completed form widget.
958
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#creating-form-elements
959
 */
960
	public function input($fieldName, $options = array()) {
961
		$this->setEntity($fieldName);
962
		$options = $this->_parseOptions($options);
963
 
964
		$divOptions = $this->_divOptions($options);
965
		unset($options['div']);
966
 
967
		if ($options['type'] === 'radio' && isset($options['options'])) {
968
			$radioOptions = (array)$options['options'];
969
			unset($options['options']);
970
		}
971
 
972
		$label = $this->_getLabel($fieldName, $options);
973
		if ($options['type'] !== 'radio') {
974
			unset($options['label']);
975
		}
976
 
977
		$error = $this->_extractOption('error', $options, null);
978
		unset($options['error']);
979
 
980
		$errorMessage = $this->_extractOption('errorMessage', $options, true);
981
		unset($options['errorMessage']);
982
 
983
		$selected = $this->_extractOption('selected', $options, null);
984
		unset($options['selected']);
985
 
986
		if ($options['type'] === 'datetime' || $options['type'] === 'date' || $options['type'] === 'time') {
987
			$dateFormat = $this->_extractOption('dateFormat', $options, 'MDY');
988
			$timeFormat = $this->_extractOption('timeFormat', $options, 12);
989
			unset($options['dateFormat'], $options['timeFormat']);
990
		}
991
 
992
		$type = $options['type'];
993
		$out = array('before' => $options['before'], 'label' => $label, 'between' => $options['between'], 'after' => $options['after']);
994
		$format = $this->_getFormat($options);
995
 
996
		unset($options['type'], $options['before'], $options['between'], $options['after'], $options['format']);
997
 
998
		$out['error'] = null;
999
		if ($type !== 'hidden' && $error !== false) {
1000
			$errMsg = $this->error($fieldName, $error);
1001
			if ($errMsg) {
1002
				$divOptions = $this->addClass($divOptions, 'error');
1003
				if ($errorMessage) {
1004
					$out['error'] = $errMsg;
1005
				}
1006
			}
1007
		}
1008
 
1009
		if ($type === 'radio' && isset($out['between'])) {
1010
			$options['between'] = $out['between'];
1011
			$out['between'] = null;
1012
		}
1013
		$out['input'] = $this->_getInput(compact('type', 'fieldName', 'options', 'radioOptions', 'selected', 'dateFormat', 'timeFormat'));
1014
 
1015
		$output = '';
1016
		foreach ($format as $element) {
1017
			$output .= $out[$element];
1018
		}
1019
 
1020
		if (!empty($divOptions['tag'])) {
1021
			$tag = $divOptions['tag'];
1022
			unset($divOptions['tag']);
1023
			$output = $this->Html->tag($tag, $output, $divOptions);
1024
		}
1025
		return $output;
1026
	}
1027
 
1028
/**
1029
 * Generates an input element
1030
 *
1031
 * @param array $args The options for the input element
1032
 * @return string The generated input element
1033
 */
1034
	protected function _getInput($args) {
1035
		extract($args);
1036
		switch ($type) {
1037
			case 'hidden':
1038
				return $this->hidden($fieldName, $options);
1039
			case 'checkbox':
1040
				return $this->checkbox($fieldName, $options);
1041
			case 'radio':
1042
				return $this->radio($fieldName, $radioOptions, $options);
1043
			case 'file':
1044
				return $this->file($fieldName, $options);
1045
			case 'select':
1046
				$options += array('options' => array(), 'value' => $selected);
1047
				$list = $options['options'];
1048
				unset($options['options']);
1049
				return $this->select($fieldName, $list, $options);
1050
			case 'time':
1051
				$options['value'] = $selected;
1052
				return $this->dateTime($fieldName, null, $timeFormat, $options);
1053
			case 'date':
1054
				$options['value'] = $selected;
1055
				return $this->dateTime($fieldName, $dateFormat, null, $options);
1056
			case 'datetime':
1057
				$options['value'] = $selected;
1058
				return $this->dateTime($fieldName, $dateFormat, $timeFormat, $options);
1059
			case 'textarea':
1060
				return $this->textarea($fieldName, $options + array('cols' => '30', 'rows' => '6'));
1061
			case 'url':
1062
				return $this->text($fieldName, array('type' => 'url') + $options);
1063
			default:
1064
				return $this->{$type}($fieldName, $options);
1065
		}
1066
	}
1067
 
1068
/**
1069
 * Generates input options array
1070
 *
1071
 * @param array $options
1072
 * @return array Options
1073
 */
1074
	protected function _parseOptions($options) {
1075
		$options = array_merge(
1076
			array('before' => null, 'between' => null, 'after' => null, 'format' => null),
1077
			$this->_inputDefaults,
1078
			$options
1079
		);
1080
 
1081
		if (!isset($options['type'])) {
1082
			$options = $this->_magicOptions($options);
1083
		}
1084
 
1085
		if (in_array($options['type'], array('radio', 'select'))) {
1086
			$options = $this->_optionsOptions($options);
1087
		}
1088
 
1089
		if (isset($options['rows']) || isset($options['cols'])) {
1090
			$options['type'] = 'textarea';
1091
		}
1092
 
1093
		if ($options['type'] === 'datetime' || $options['type'] === 'date' || $options['type'] === 'time' || $options['type'] === 'select') {
1094
			$options += array('empty' => false);
1095
		}
1096
		return $options;
1097
	}
1098
 
1099
/**
1100
 * Generates list of options for multiple select
1101
 *
1102
 * @param array $options
1103
 * @return array
1104
 */
1105
	protected function _optionsOptions($options) {
1106
		if (isset($options['options'])) {
1107
			return $options;
1108
		}
1109
		$varName = Inflector::variable(
1110
			Inflector::pluralize(preg_replace('/_id$/', '', $this->field()))
1111
		);
1112
		$varOptions = $this->_View->get($varName);
1113
		if (!is_array($varOptions)) {
1114
			return $options;
1115
		}
1116
		if ($options['type'] !== 'radio') {
1117
			$options['type'] = 'select';
1118
		}
1119
		$options['options'] = $varOptions;
1120
		return $options;
1121
	}
1122
 
1123
/**
1124
 * Magically set option type and corresponding options
1125
 *
1126
 * @param array $options
1127
 * @return array
1128
 */
1129
	protected function _magicOptions($options) {
1130
		$modelKey = $this->model();
1131
		$fieldKey = $this->field();
1132
		$options['type'] = 'text';
1133
		if (isset($options['options'])) {
1134
			$options['type'] = 'select';
1135
		} elseif (in_array($fieldKey, array('psword', 'passwd', 'password'))) {
1136
			$options['type'] = 'password';
1137
		} elseif (in_array($fieldKey, array('tel', 'telephone', 'phone'))) {
1138
			$options['type'] = 'tel';
1139
		} elseif ($fieldKey === 'email') {
1140
			$options['type'] = 'email';
1141
		} elseif (isset($options['checked'])) {
1142
			$options['type'] = 'checkbox';
1143
		} elseif ($fieldDef = $this->_introspectModel($modelKey, 'fields', $fieldKey)) {
1144
			$type = $fieldDef['type'];
1145
			$primaryKey = $this->fieldset[$modelKey]['key'];
1146
			$map = array(
1147
				'string' => 'text', 'datetime' => 'datetime',
1148
				'boolean' => 'checkbox', 'timestamp' => 'datetime',
1149
				'text' => 'textarea', 'time' => 'time',
1150
				'date' => 'date', 'float' => 'number',
1151
				'integer' => 'number'
1152
			);
1153
 
1154
			if (isset($this->map[$type])) {
1155
				$options['type'] = $this->map[$type];
1156
			} elseif (isset($map[$type])) {
1157
				$options['type'] = $map[$type];
1158
			}
1159
			if ($fieldKey == $primaryKey) {
1160
				$options['type'] = 'hidden';
1161
			}
1162
			if (
1163
				$options['type'] === 'number' &&
1164
				$type === 'float' &&
1165
				!isset($options['step'])
1166
			) {
1167
				$options['step'] = 'any';
1168
			}
1169
		}
1170
 
1171
		if (preg_match('/_id$/', $fieldKey) && $options['type'] !== 'hidden') {
1172
			$options['type'] = 'select';
1173
		}
1174
 
1175
		if ($modelKey === $fieldKey) {
1176
			$options['type'] = 'select';
1177
			if (!isset($options['multiple'])) {
1178
				$options['multiple'] = 'multiple';
1179
			}
1180
		}
1181
		if (in_array($options['type'], array('text', 'number'))) {
1182
			$options = $this->_optionsOptions($options);
1183
		}
1184
		if ($options['type'] === 'select' && array_key_exists('step', $options)) {
1185
			unset($options['step']);
1186
		}
1187
		$options = $this->_maxLength($options);
1188
		return $options;
1189
	}
1190
 
1191
/**
1192
 * Generate format options
1193
 *
1194
 * @param array $options
1195
 * @return array
1196
 */
1197
	protected function _getFormat($options) {
1198
		if ($options['type'] === 'hidden') {
1199
			return array('input');
1200
		}
1201
		if (is_array($options['format']) && in_array('input', $options['format'])) {
1202
			return $options['format'];
1203
		}
1204
		if ($options['type'] === 'checkbox') {
1205
			return array('before', 'input', 'between', 'label', 'after', 'error');
1206
		}
1207
		return array('before', 'label', 'between', 'input', 'after', 'error');
1208
	}
1209
 
1210
/**
1211
 * Generate label for input
1212
 *
1213
 * @param string $fieldName
1214
 * @param array $options
1215
 * @return boolean|string false or Generated label element
1216
 */
1217
	protected function _getLabel($fieldName, $options) {
1218
		if ($options['type'] === 'radio') {
1219
			return false;
1220
		}
1221
 
1222
		$label = null;
1223
		if (isset($options['label'])) {
1224
			$label = $options['label'];
1225
		}
1226
 
1227
		if ($label === false) {
1228
			return false;
1229
		}
1230
		return $this->_inputLabel($fieldName, $label, $options);
1231
	}
1232
 
1233
/**
1234
 * Calculates maxlength option
1235
 *
1236
 * @param array $options
1237
 * @return array
1238
 */
1239
	protected function _maxLength($options) {
1240
		$fieldDef = $this->_introspectModel($this->model(), 'fields', $this->field());
1241
		$autoLength = (
1242
			!array_key_exists('maxlength', $options) &&
1243
			isset($fieldDef['length']) &&
1244
			is_scalar($fieldDef['length']) &&
1245
			$options['type'] !== 'select'
1246
		);
1247
		if ($autoLength &&
1248
			in_array($options['type'], array('text', 'email', 'tel', 'url', 'search'))
1249
		) {
1250
			$options['maxlength'] = $fieldDef['length'];
1251
		}
1252
		return $options;
1253
	}
1254
 
1255
/**
1256
 * Generate div options for input
1257
 *
1258
 * @param array $options
1259
 * @return array
1260
 */
1261
	protected function _divOptions($options) {
1262
		if ($options['type'] === 'hidden') {
1263
			return array();
1264
		}
1265
		$div = $this->_extractOption('div', $options, true);
1266
		if (!$div) {
1267
			return array();
1268
		}
1269
 
1270
		$divOptions = array('class' => 'input');
1271
		$divOptions = $this->addClass($divOptions, $options['type']);
1272
		if (is_string($div)) {
1273
			$divOptions['class'] = $div;
1274
		} elseif (is_array($div)) {
1275
			$divOptions = array_merge($divOptions, $div);
1276
		}
1277
		if (
1278
			$this->_extractOption('required', $options) !== false &&
1279
			$this->_introspectModel($this->model(), 'validates', $this->field())
1280
		) {
1281
			$divOptions = $this->addClass($divOptions, 'required');
1282
		}
1283
		if (!isset($divOptions['tag'])) {
1284
			$divOptions['tag'] = 'div';
1285
		}
1286
		return $divOptions;
1287
	}
1288
 
1289
/**
1290
 * Extracts a single option from an options array.
1291
 *
1292
 * @param string $name The name of the option to pull out.
1293
 * @param array $options The array of options you want to extract.
1294
 * @param mixed $default The default option value
1295
 * @return mixed the contents of the option or default
1296
 */
1297
	protected function _extractOption($name, $options, $default = null) {
1298
		if (array_key_exists($name, $options)) {
1299
			return $options[$name];
1300
		}
1301
		return $default;
1302
	}
1303
 
1304
/**
1305
 * Generate a label for an input() call.
1306
 *
1307
 * $options can contain a hash of id overrides. These overrides will be
1308
 * used instead of the generated values if present.
1309
 *
1310
 * @param string $fieldName
1311
 * @param string $label
1312
 * @param array $options Options for the label element. 'NONE' option is deprecated and will be removed in 3.0
1313
 * @return string Generated label element
1314
 */
1315
	protected function _inputLabel($fieldName, $label, $options) {
1316
		$labelAttributes = $this->domId(array(), 'for');
1317
		$idKey = null;
1318
		if ($options['type'] === 'date' || $options['type'] === 'datetime') {
1319
			$firstInput = 'M';
1320
			if (
1321
				array_key_exists('dateFormat', $options) &&
1322
				($options['dateFormat'] === null || $options['dateFormat'] === 'NONE')
1323
			) {
1324
				$firstInput = 'H';
1325
			} elseif (!empty($options['dateFormat'])) {
1326
				$firstInput = substr($options['dateFormat'], 0, 1);
1327
			}
1328
			switch ($firstInput) {
1329
				case 'D':
1330
					$idKey = 'day';
1331
					$labelAttributes['for'] .= 'Day';
1332
					break;
1333
				case 'Y':
1334
					$idKey = 'year';
1335
					$labelAttributes['for'] .= 'Year';
1336
					break;
1337
				case 'M':
1338
					$idKey = 'month';
1339
					$labelAttributes['for'] .= 'Month';
1340
					break;
1341
				case 'H':
1342
					$idKey = 'hour';
1343
					$labelAttributes['for'] .= 'Hour';
1344
			}
1345
		}
1346
		if ($options['type'] === 'time') {
1347
			$labelAttributes['for'] .= 'Hour';
1348
			$idKey = 'hour';
1349
		}
1350
		if (isset($idKey) && isset($options['id']) && isset($options['id'][$idKey])) {
1351
			$labelAttributes['for'] = $options['id'][$idKey];
1352
		}
1353
 
1354
		if (is_array($label)) {
1355
			$labelText = null;
1356
			if (isset($label['text'])) {
1357
				$labelText = $label['text'];
1358
				unset($label['text']);
1359
			}
1360
			$labelAttributes = array_merge($labelAttributes, $label);
1361
		} else {
1362
			$labelText = $label;
1363
		}
1364
 
1365
		if (isset($options['id']) && is_string($options['id'])) {
1366
			$labelAttributes = array_merge($labelAttributes, array('for' => $options['id']));
1367
		}
1368
		return $this->label($fieldName, $labelText, $labelAttributes);
1369
	}
1370
 
1371
/**
1372
 * Creates a checkbox input widget.
1373
 *
1374
 * ### Options:
1375
 *
1376
 * - `value` - the value of the checkbox
1377
 * - `checked` - boolean indicate that this checkbox is checked.
1378
 * - `hiddenField` - boolean to indicate if you want the results of checkbox() to include
1379
 *    a hidden input with a value of ''.
1380
 * - `disabled` - create a disabled input.
1381
 * - `default` - Set the default value for the checkbox. This allows you to start checkboxes
1382
 *    as checked, without having to check the POST data. A matching POST data value, will overwrite
1383
 *    the default value.
1384
 *
1385
 * @param string $fieldName Name of a field, like this "Modelname.fieldname"
1386
 * @param array $options Array of HTML attributes.
1387
 * @return string An HTML text input element.
1388
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#options-for-select-checkbox-and-radio-inputs
1389
 */
1390
	public function checkbox($fieldName, $options = array()) {
1391
		$valueOptions = array();
1392
		if (isset($options['default'])) {
1393
			$valueOptions['default'] = $options['default'];
1394
			unset($options['default']);
1395
		}
1396
 
1397
		$options += array('required' => false);
1398
		$options = $this->_initInputField($fieldName, $options) + array('hiddenField' => true);
1399
		$value = current($this->value($valueOptions));
1400
		$output = '';
1401
 
1402
		if (empty($options['value'])) {
1403
			$options['value'] = 1;
1404
		}
1405
		if (
1406
			(!isset($options['checked']) && !empty($value) && $value == $options['value']) ||
1407
			!empty($options['checked'])
1408
		) {
1409
			$options['checked'] = 'checked';
1410
		}
1411
		if ($options['hiddenField']) {
1412
			$hiddenOptions = array(
1413
				'id' => $options['id'] . '_',
1414
				'name' => $options['name'],
1415
				'value' => ($options['hiddenField'] !== true ? $options['hiddenField'] : '0'),
1416
				'secure' => false
1417
			);
1418
			if (isset($options['disabled']) && $options['disabled']) {
1419
				$hiddenOptions['disabled'] = 'disabled';
1420
			}
1421
			$output = $this->hidden($fieldName, $hiddenOptions);
1422
		}
1423
		unset($options['hiddenField']);
1424
 
1425
		return $output . $this->Html->useTag('checkbox', $options['name'], array_diff_key($options, array('name' => null)));
1426
	}
1427
 
1428
/**
1429
 * Creates a set of radio widgets. Will create a legend and fieldset
1430
 * by default. Use $options to control this
1431
 *
1432
 * ### Attributes:
1433
 *
1434
 * - `separator` - define the string in between the radio buttons
1435
 * - `between` - the string between legend and input set or array of strings to insert
1436
 *    strings between each input block
1437
 * - `legend` - control whether or not the widget set has a fieldset & legend
1438
 * - `value` - indicate a value that is should be checked
1439
 * - `label` - boolean to indicate whether or not labels for widgets show be displayed
1440
 * - `hiddenField` - boolean to indicate if you want the results of radio() to include
1441
 *    a hidden input with a value of ''. This is useful for creating radio sets that non-continuous
1442
 * - `disabled` - Set to `true` or `disabled` to disable all the radio buttons.
1443
 * - `empty` - Set to `true` to create a input with the value '' as the first option. When `true`
1444
 *   the radio label will be 'empty'. Set this option to a string to control the label value.
1445
 *
1446
 * @param string $fieldName Name of a field, like this "Modelname.fieldname"
1447
 * @param array $options Radio button options array.
1448
 * @param array $attributes Array of HTML attributes, and special attributes above.
1449
 * @return string Completed radio widget set.
1450
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#options-for-select-checkbox-and-radio-inputs
1451
 */
1452
	public function radio($fieldName, $options = array(), $attributes = array()) {
1453
		$attributes = $this->_initInputField($fieldName, $attributes);
1454
 
1455
		$showEmpty = $this->_extractOption('empty', $attributes);
1456
		if ($showEmpty) {
1457
			$showEmpty = ($showEmpty === true) ? __d('cake', 'empty') : $showEmpty;
1458
			$options = array('' => $showEmpty) + $options;
1459
		}
1460
		unset($attributes['empty']);
1461
 
1462
		$legend = false;
1463
		if (isset($attributes['legend'])) {
1464
			$legend = $attributes['legend'];
1465
			unset($attributes['legend']);
1466
		} elseif (count($options) > 1) {
1467
			$legend = __(Inflector::humanize($this->field()));
1468
		}
1469
 
1470
		$label = true;
1471
		if (isset($attributes['label'])) {
1472
			$label = $attributes['label'];
1473
			unset($attributes['label']);
1474
		}
1475
 
1476
		$separator = null;
1477
		if (isset($attributes['separator'])) {
1478
			$separator = $attributes['separator'];
1479
			unset($attributes['separator']);
1480
		}
1481
 
1482
		$between = null;
1483
		if (isset($attributes['between'])) {
1484
			$between = $attributes['between'];
1485
			unset($attributes['between']);
1486
		}
1487
 
1488
		$value = null;
1489
		if (isset($attributes['value'])) {
1490
			$value = $attributes['value'];
1491
		} else {
1492
			$value = $this->value($fieldName);
1493
		}
1494
 
1495
		$disabled = array();
1496
		if (isset($attributes['disabled'])) {
1497
			$disabled = $attributes['disabled'];
1498
		}
1499
 
1500
		$out = array();
1501
 
1502
		$hiddenField = isset($attributes['hiddenField']) ? $attributes['hiddenField'] : true;
1503
		unset($attributes['hiddenField']);
1504
 
1505
		if (isset($value) && is_bool($value)) {
1506
			$value = $value ? 1 : 0;
1507
		}
1508
 
1509
		foreach ($options as $optValue => $optTitle) {
1510
			$optionsHere = array('value' => $optValue, 'disabled' => false);
1511
 
1512
			if (isset($value) && strval($optValue) === strval($value)) {
1513
				$optionsHere['checked'] = 'checked';
1514
			}
1515
			$isNumeric = is_numeric($optValue);
1516
			if ($disabled && (!is_array($disabled) || in_array((string)$optValue, $disabled, !$isNumeric))) {
1517
				$optionsHere['disabled'] = true;
1518
			}
1519
			$tagName = Inflector::camelize(
1520
				$attributes['id'] . '_' . Inflector::slug($optValue)
1521
			);
1522
 
1523
			if ($label) {
1524
				$labelOpts = is_array($label) ? $label : array();
1525
				$labelOpts += array('for' => $tagName);
1526
				$optTitle = $this->label($tagName, $optTitle, $labelOpts);
1527
			}
1528
 
1529
			if (is_array($between)) {
1530
				$optTitle .= array_shift($between);
1531
			}
1532
			$allOptions = array_merge($attributes, $optionsHere);
1533
			$out[] = $this->Html->useTag('radio', $attributes['name'], $tagName,
1534
				array_diff_key($allOptions, array('name' => null, 'type' => null, 'id' => null)),
1535
				$optTitle
1536
			);
1537
		}
1538
		$hidden = null;
1539
 
1540
		if ($hiddenField) {
1541
			if (!isset($value) || $value === '') {
1542
				$hidden = $this->hidden($fieldName, array(
1543
					'id' => $attributes['id'] . '_', 'value' => '', 'name' => $attributes['name']
1544
				));
1545
			}
1546
		}
1547
		$out = $hidden . implode($separator, $out);
1548
 
1549
		if (is_array($between)) {
1550
			$between = '';
1551
		}
1552
		if ($legend) {
1553
			$out = $this->Html->useTag('fieldset', '', $this->Html->useTag('legend', $legend) . $between . $out);
1554
		}
1555
		return $out;
1556
	}
1557
 
1558
/**
1559
 * Missing method handler - implements various simple input types. Is used to create inputs
1560
 * of various types. e.g. `$this->Form->text();` will create `<input type="text" />` while
1561
 * `$this->Form->range();` will create `<input type="range" />`
1562
 *
1563
 * ### Usage
1564
 *
1565
 * `$this->Form->search('User.query', array('value' => 'test'));`
1566
 *
1567
 * Will make an input like:
1568
 *
1569
 * `<input type="search" id="UserQuery" name="data[User][query]" value="test" />`
1570
 *
1571
 * The first argument to an input type should always be the fieldname, in `Model.field` format.
1572
 * The second argument should always be an array of attributes for the input.
1573
 *
1574
 * @param string $method Method name / input type to make.
1575
 * @param array $params Parameters for the method call
1576
 * @return string Formatted input method.
1577
 * @throws CakeException When there are no params for the method call.
1578
 */
1579
	public function __call($method, $params) {
1580
		$options = array();
1581
		if (empty($params)) {
1582
			throw new CakeException(__d('cake_dev', 'Missing field name for FormHelper::%s', $method));
1583
		}
1584
		if (isset($params[1])) {
1585
			$options = $params[1];
1586
		}
1587
		if (!isset($options['type'])) {
1588
			$options['type'] = $method;
1589
		}
1590
		$options = $this->_initInputField($params[0], $options);
1591
		return $this->Html->useTag('input', $options['name'], array_diff_key($options, array('name' => null)));
1592
	}
1593
 
1594
/**
1595
 * Creates a textarea widget.
1596
 *
1597
 * ### Options:
1598
 *
1599
 * - `escape` - Whether or not the contents of the textarea should be escaped. Defaults to true.
1600
 *
1601
 * @param string $fieldName Name of a field, in the form "Modelname.fieldname"
1602
 * @param array $options Array of HTML attributes, and special options above.
1603
 * @return string A generated HTML text input element
1604
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::textarea
1605
 */
1606
	public function textarea($fieldName, $options = array()) {
1607
		$options = $this->_initInputField($fieldName, $options);
1608
		$value = null;
1609
 
1610
		if (array_key_exists('value', $options)) {
1611
			$value = $options['value'];
1612
			if (!array_key_exists('escape', $options) || $options['escape'] !== false) {
1613
				$value = h($value);
1614
			}
1615
			unset($options['value']);
1616
		}
1617
		return $this->Html->useTag('textarea', $options['name'], array_diff_key($options, array('type' => null, 'name' => null)), $value);
1618
	}
1619
 
1620
/**
1621
 * Creates a hidden input field.
1622
 *
1623
 * @param string $fieldName Name of a field, in the form of "Modelname.fieldname"
1624
 * @param array $options Array of HTML attributes.
1625
 * @return string A generated hidden input
1626
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::hidden
1627
 */
1628
	public function hidden($fieldName, $options = array()) {
1629
		$options += array('required' => false, 'secure' => true);
1630
 
1631
		$secure = $options['secure'];
1632
		unset($options['secure']);
1633
 
1634
		$options = $this->_initInputField($fieldName, array_merge(
1635
			$options, array('secure' => self::SECURE_SKIP)
1636
		));
1637
 
1638
		if ($secure === true) {
1639
			$this->_secure(true, null, '' . $options['value']);
1640
		}
1641
 
1642
		return $this->Html->useTag('hidden', $options['name'], array_diff_key($options, array('name' => null)));
1643
	}
1644
 
1645
/**
1646
 * Creates file input widget.
1647
 *
1648
 * @param string $fieldName Name of a field, in the form "Modelname.fieldname"
1649
 * @param array $options Array of HTML attributes.
1650
 * @return string A generated file input.
1651
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::file
1652
 */
1653
	public function file($fieldName, $options = array()) {
1654
		$options += array('secure' => true);
1655
		$secure = $options['secure'];
1656
		$options['secure'] = self::SECURE_SKIP;
1657
 
1658
		$options = $this->_initInputField($fieldName, $options);
1659
		$field = $this->entity();
1660
 
1661
		foreach (array('name', 'type', 'tmp_name', 'error', 'size') as $suffix) {
1662
			$this->_secure($secure, array_merge($field, array($suffix)));
1663
		}
1664
 
1665
		$exclude = array('name' => null, 'value' => null);
1666
		return $this->Html->useTag('file', $options['name'], array_diff_key($options, $exclude));
1667
	}
1668
 
1669
/**
1670
 * Creates a `<button>` tag. The type attribute defaults to `type="submit"`
1671
 * You can change it to a different value by using `$options['type']`.
1672
 *
1673
 * ### Options:
1674
 *
1675
 * - `escape` - HTML entity encode the $title of the button. Defaults to false.
1676
 *
1677
 * @param string $title The button's caption. Not automatically HTML encoded
1678
 * @param array $options Array of options and HTML attributes.
1679
 * @return string A HTML button tag.
1680
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::button
1681
 */
1682
	public function button($title, $options = array()) {
1683
		$options += array('type' => 'submit', 'escape' => false, 'secure' => false);
1684
		if ($options['escape']) {
1685
			$title = h($title);
1686
		}
1687
		if (isset($options['name'])) {
1688
			$name = str_replace(array('[', ']'), array('.', ''), $options['name']);
1689
			$this->_secure($options['secure'], $name);
1690
		}
1691
		return $this->Html->useTag('button', $options, $title);
1692
	}
1693
 
1694
/**
1695
 * Create a `<button>` tag with a surrounding `<form>` that submits via POST.
1696
 *
1697
 * This method creates a `<form>` element. So do not use this method in an already opened form.
1698
 * Instead use FormHelper::submit() or FormHelper::button() to create buttons inside opened forms.
1699
 *
1700
 * ### Options:
1701
 *
1702
 * - `data` - Array with key/value to pass in input hidden
1703
 * - Other options is the same of button method.
1704
 *
1705
 * @param string $title The button's caption. Not automatically HTML encoded
1706
 * @param string|array $url URL as string or array
1707
 * @param array $options Array of options and HTML attributes.
1708
 * @return string A HTML button tag.
1709
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::postButton
1710
 */
1711
	public function postButton($title, $url, $options = array()) {
1712
		$out = $this->create(false, array('id' => false, 'url' => $url));
1713
		if (isset($options['data']) && is_array($options['data'])) {
1714
			foreach ($options['data'] as $key => $value) {
1715
				$out .= $this->hidden($key, array('value' => $value, 'id' => false));
1716
			}
1717
			unset($options['data']);
1718
		}
1719
		$out .= $this->button($title, $options);
1720
		$out .= $this->end();
1721
		return $out;
1722
	}
1723
 
1724
/**
1725
 * Creates an HTML link, but access the URL using the method you specify (defaults to POST).
1726
 * Requires javascript to be enabled in browser.
1727
 *
1728
 * This method creates a `<form>` element. So do not use this method inside an existing form.
1729
 * Instead you should add a submit button using FormHelper::submit()
1730
 *
1731
 * ### Options:
1732
 *
1733
 * - `data` - Array with key/value to pass in input hidden
1734
 * - `method` - Request method to use. Set to 'delete' to simulate HTTP/1.1 DELETE request. Defaults to 'post'.
1735
 * - `confirm` - Can be used instead of $confirmMessage.
1736
 * - Other options is the same of HtmlHelper::link() method.
1737
 * - The option `onclick` will be replaced.
1738
 *
1739
 * @param string $title The content to be wrapped by <a> tags.
1740
 * @param string|array $url Cake-relative URL or array of URL parameters, or external URL (starts with http://)
1741
 * @param array $options Array of HTML attributes.
1742
 * @param boolean|string $confirmMessage JavaScript confirmation message.
1743
 * @return string An `<a />` element.
1744
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::postLink
1745
 */
1746
	public function postLink($title, $url = null, $options = array(), $confirmMessage = false) {
1747
		$requestMethod = 'POST';
1748
		if (!empty($options['method'])) {
1749
			$requestMethod = strtoupper($options['method']);
1750
			unset($options['method']);
1751
		}
1752
		if (!empty($options['confirm'])) {
1753
			$confirmMessage = $options['confirm'];
1754
			unset($options['confirm']);
1755
		}
1756
 
1757
		$formName = str_replace('.', '', uniqid('post_', true));
1758
		$formUrl = $this->url($url);
1759
		$formOptions = array(
1760
			'name' => $formName,
1761
			'id' => $formName,
1762
			'style' => 'display:none;',
1763
			'method' => 'post',
1764
		);
1765
		if (isset($options['target'])) {
1766
			$formOptions['target'] = $options['target'];
1767
			unset($options['target']);
1768
		}
1769
 
1770
		$out = $this->Html->useTag('form', $formUrl, $formOptions);
1771
		$out .= $this->Html->useTag('hidden', '_method', array(
1772
			'value' => $requestMethod
1773
		));
1774
		$out .= $this->_csrfField();
1775
 
1776
		$fields = array();
1777
		if (isset($options['data']) && is_array($options['data'])) {
1778
			foreach ($options['data'] as $key => $value) {
1779
				$fields[$key] = $value;
1780
				$out .= $this->hidden($key, array('value' => $value, 'id' => false));
1781
			}
1782
			unset($options['data']);
1783
		}
1784
		$out .= $this->secure($fields);
1785
		$out .= $this->Html->useTag('formend');
1786
 
1787
		$url = '#';
1788
		$onClick = 'document.' . $formName . '.submit();';
1789
		if ($confirmMessage) {
1790
			$options['onclick'] = $this->_confirm($confirmMessage, $onClick, '', $options);
1791
		} else {
1792
			$options['onclick'] = $onClick . ' ';
1793
		}
1794
		$options['onclick'] .= 'event.returnValue = false; return false;';
1795
 
1796
		$out .= $this->Html->link($title, $url, $options);
1797
		return $out;
1798
	}
1799
 
1800
/**
1801
 * Creates a submit button element. This method will generate `<input />` elements that
1802
 * can be used to submit, and reset forms by using $options. image submits can be created by supplying an
1803
 * image path for $caption.
1804
 *
1805
 * ### Options
1806
 *
1807
 * - `div` - Include a wrapping div?  Defaults to true. Accepts sub options similar to
1808
 *   FormHelper::input().
1809
 * - `before` - Content to include before the input.
1810
 * - `after` - Content to include after the input.
1811
 * - `type` - Set to 'reset' for reset inputs. Defaults to 'submit'
1812
 * - Other attributes will be assigned to the input element.
1813
 *
1814
 * ### Options
1815
 *
1816
 * - `div` - Include a wrapping div?  Defaults to true. Accepts sub options similar to
1817
 *   FormHelper::input().
1818
 * - Other attributes will be assigned to the input element.
1819
 *
1820
 * @param string $caption The label appearing on the button OR if string contains :// or the
1821
 *  extension .jpg, .jpe, .jpeg, .gif, .png use an image if the extension
1822
 *  exists, AND the first character is /, image is relative to webroot,
1823
 *  OR if the first character is not /, image is relative to webroot/img.
1824
 * @param array $options Array of options. See above.
1825
 * @return string A HTML submit button
1826
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::submit
1827
 */
1828
	public function submit($caption = null, $options = array()) {
1829
		if (!is_string($caption) && empty($caption)) {
1830
			$caption = __d('cake', 'Submit');
1831
		}
1832
		$out = null;
1833
		$div = true;
1834
 
1835
		if (isset($options['div'])) {
1836
			$div = $options['div'];
1837
			unset($options['div']);
1838
		}
1839
		$options += array('type' => 'submit', 'before' => null, 'after' => null, 'secure' => false);
1840
		$divOptions = array('tag' => 'div');
1841
 
1842
		if ($div === true) {
1843
			$divOptions['class'] = 'submit';
1844
		} elseif ($div === false) {
1845
			unset($divOptions);
1846
		} elseif (is_string($div)) {
1847
			$divOptions['class'] = $div;
1848
		} elseif (is_array($div)) {
1849
			$divOptions = array_merge(array('class' => 'submit', 'tag' => 'div'), $div);
1850
		}
1851
 
1852
		if (isset($options['name'])) {
1853
			$name = str_replace(array('[', ']'), array('.', ''), $options['name']);
1854
			$this->_secure($options['secure'], $name);
1855
		}
1856
		unset($options['secure']);
1857
 
1858
		$before = $options['before'];
1859
		$after = $options['after'];
1860
		unset($options['before'], $options['after']);
1861
 
1862
		$isUrl = strpos($caption, '://') !== false;
1863
		$isImage = preg_match('/\.(jpg|jpe|jpeg|gif|png|ico)$/', $caption);
1864
 
1865
		if ($isUrl || $isImage) {
1866
			$unlockFields = array('x', 'y');
1867
			if (isset($options['name'])) {
1868
				$unlockFields = array(
1869
					$options['name'] . '_x', $options['name'] . '_y'
1870
				);
1871
			}
1872
			foreach ($unlockFields as $ignore) {
1873
				$this->unlockField($ignore);
1874
			}
1875
		}
1876
 
1877
		if ($isUrl) {
1878
			unset($options['type']);
1879
			$tag = $this->Html->useTag('submitimage', $caption, $options);
1880
		} elseif ($isImage) {
1881
			unset($options['type']);
1882
			if ($caption{0} !== '/') {
1883
				$url = $this->webroot(Configure::read('App.imageBaseUrl') . $caption);
1884
			} else {
1885
				$url = $this->webroot(trim($caption, '/'));
1886
			}
1887
			$url = $this->assetTimestamp($url);
1888
			$tag = $this->Html->useTag('submitimage', $url, $options);
1889
		} else {
1890
			$options['value'] = $caption;
1891
			$tag = $this->Html->useTag('submit', $options);
1892
		}
1893
		$out = $before . $tag . $after;
1894
 
1895
		if (isset($divOptions)) {
1896
			$tag = $divOptions['tag'];
1897
			unset($divOptions['tag']);
1898
			$out = $this->Html->tag($tag, $out, $divOptions);
1899
		}
1900
		return $out;
1901
	}
1902
 
1903
/**
1904
 * Returns a formatted SELECT element.
1905
 *
1906
 * ### Attributes:
1907
 *
1908
 * - `showParents` - If included in the array and set to true, an additional option element
1909
 *   will be added for the parent of each option group. You can set an option with the same name
1910
 *   and it's key will be used for the value of the option.
1911
 * - `multiple` - show a multiple select box. If set to 'checkbox' multiple checkboxes will be
1912
 *   created instead.
1913
 * - `empty` - If true, the empty select option is shown. If a string,
1914
 *   that string is displayed as the empty element.
1915
 * - `escape` - If true contents of options will be HTML entity encoded. Defaults to true.
1916
 * - `value` The selected value of the input.
1917
 * - `class` - When using multiple = checkbox the class name to apply to the divs. Defaults to 'checkbox'.
1918
 * - `disabled` - Control the disabled attribute. When creating a select box, set to true to disable the
1919
 *   select box. When creating checkboxes, `true` will disable all checkboxes. You can also set disabled
1920
 *   to a list of values you want to disable when creating checkboxes.
1921
 *
1922
 * ### Using options
1923
 *
1924
 * A simple array will create normal options:
1925
 *
1926
 * {{{
1927
 * $options = array(1 => 'one', 2 => 'two);
1928
 * $this->Form->select('Model.field', $options));
1929
 * }}}
1930
 *
1931
 * While a nested options array will create optgroups with options inside them.
1932
 * {{{
1933
 * $options = array(
1934
 *  1 => 'bill',
1935
 *  'fred' => array(
1936
 *     2 => 'fred',
1937
 *     3 => 'fred jr.'
1938
 *  )
1939
 * );
1940
 * $this->Form->select('Model.field', $options);
1941
 * }}}
1942
 *
1943
 * In the above `2 => 'fred'` will not generate an option element. You should enable the `showParents`
1944
 * attribute to show the fred option.
1945
 *
1946
 * If you have multiple options that need to have the same value attribute, you can
1947
 * use an array of arrays to express this:
1948
 *
1949
 * {{{
1950
 * $options = array(
1951
 *  array('name' => 'United states', 'value' => 'USA'),
1952
 *  array('name' => 'USA', 'value' => 'USA'),
1953
 * );
1954
 * }}}
1955
 *
1956
 * @param string $fieldName Name attribute of the SELECT
1957
 * @param array $options Array of the OPTION elements (as 'value'=>'Text' pairs) to be used in the
1958
 *	SELECT element
1959
 * @param array $attributes The HTML attributes of the select element.
1960
 * @return string Formatted SELECT element
1961
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#options-for-select-checkbox-and-radio-inputs
1962
 */
1963
	public function select($fieldName, $options = array(), $attributes = array()) {
1964
		$select = array();
1965
		$style = null;
1966
		$tag = null;
1967
		$attributes += array(
1968
			'class' => null,
1969
			'escape' => true,
1970
			'secure' => true,
1971
			'empty' => '',
1972
			'showParents' => false,
1973
			'hiddenField' => true,
1974
			'disabled' => false
1975
		);
1976
 
1977
		$escapeOptions = $this->_extractOption('escape', $attributes);
1978
		$secure = $this->_extractOption('secure', $attributes);
1979
		$showEmpty = $this->_extractOption('empty', $attributes);
1980
		$showParents = $this->_extractOption('showParents', $attributes);
1981
		$hiddenField = $this->_extractOption('hiddenField', $attributes);
1982
		unset($attributes['escape'], $attributes['secure'], $attributes['empty'], $attributes['showParents'], $attributes['hiddenField']);
1983
		$id = $this->_extractOption('id', $attributes);
1984
 
1985
		$attributes = $this->_initInputField($fieldName, array_merge(
1986
			(array)$attributes, array('secure' => self::SECURE_SKIP)
1987
		));
1988
 
1989
		if (is_string($options) && isset($this->_options[$options])) {
1990
			$options = $this->_generateOptions($options);
1991
		} elseif (!is_array($options)) {
1992
			$options = array();
1993
		}
1994
		if (isset($attributes['type'])) {
1995
			unset($attributes['type']);
1996
		}
1997
 
1998
		if (!empty($attributes['multiple'])) {
1999
			$style = ($attributes['multiple'] === 'checkbox') ? 'checkbox' : null;
2000
			$template = ($style) ? 'checkboxmultiplestart' : 'selectmultiplestart';
2001
			$tag = $template;
2002
			if ($hiddenField) {
2003
				$hiddenAttributes = array(
2004
					'value' => '',
2005
					'id' => $attributes['id'] . ($style ? '' : '_'),
2006
					'secure' => false,
2007
					'name' => $attributes['name']
2008
				);
2009
				$select[] = $this->hidden(null, $hiddenAttributes);
2010
			}
2011
		} else {
2012
			$tag = 'selectstart';
2013
		}
2014
 
2015
		if ($tag === 'checkboxmultiplestart') {
2016
			unset($attributes['required']);
2017
		}
2018
 
2019
		if (!empty($tag) || isset($template)) {
2020
			$hasOptions = (count($options) > 0 || $showEmpty);
2021
			// Secure the field if there are options, or its a multi select.
2022
			// Single selects with no options don't submit, but multiselects do.
2023
			if (
2024
				(!isset($secure) || $secure) &&
2025
				empty($attributes['disabled']) &&
2026
				(!empty($attributes['multiple']) || $hasOptions)
2027
			) {
2028
				$this->_secure(true, $this->_secureFieldName($attributes));
2029
			}
2030
			$select[] = $this->Html->useTag($tag, $attributes['name'], array_diff_key($attributes, array('name' => null, 'value' => null)));
2031
		}
2032
		$emptyMulti = (
2033
			$showEmpty !== null && $showEmpty !== false && !(
2034
				empty($showEmpty) && (isset($attributes) &&
2035
				array_key_exists('multiple', $attributes))
2036
			)
2037
		);
2038
 
2039
		if ($emptyMulti) {
2040
			$showEmpty = ($showEmpty === true) ? '' : $showEmpty;
2041
			$options = array('' => $showEmpty) + $options;
2042
		}
2043
 
2044
		if (!$id) {
2045
			$attributes['id'] = Inflector::camelize($attributes['id']);
2046
		}
2047
 
2048
		$select = array_merge($select, $this->_selectOptions(
2049
			array_reverse($options, true),
2050
			array(),
2051
			$showParents,
2052
			array(
2053
				'escape' => $escapeOptions,
2054
				'style' => $style,
2055
				'name' => $attributes['name'],
2056
				'value' => $attributes['value'],
2057
				'class' => $attributes['class'],
2058
				'id' => $attributes['id'],
2059
				'disabled' => $attributes['disabled'],
2060
			)
2061
		));
2062
 
2063
		$template = ($style === 'checkbox') ? 'checkboxmultipleend' : 'selectend';
2064
		$select[] = $this->Html->useTag($template);
2065
		return implode("\n", $select);
2066
	}
2067
 
2068
/**
2069
 * Returns a SELECT element for days.
2070
 *
2071
 * ### Attributes:
2072
 *
2073
 * - `empty` - If true, the empty select option is shown. If a string,
2074
 *   that string is displayed as the empty element.
2075
 * - `value` The selected value of the input.
2076
 *
2077
 * @param string $fieldName Prefix name for the SELECT element
2078
 * @param array $attributes HTML attributes for the select element
2079
 * @return string A generated day select box.
2080
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::day
2081
 */
2082
	public function day($fieldName = null, $attributes = array()) {
2083
		$attributes += array('empty' => true, 'value' => null);
2084
		$attributes = $this->_dateTimeSelected('day', $fieldName, $attributes);
2085
 
2086
		if (strlen($attributes['value']) > 2) {
2087
			$attributes['value'] = date_create($attributes['value'])->format('d');
2088
		} elseif ($attributes['value'] === false) {
2089
			$attributes['value'] = null;
2090
		}
2091
		return $this->select($fieldName . ".day", $this->_generateOptions('day'), $attributes);
2092
	}
2093
 
2094
/**
2095
 * Returns a SELECT element for years
2096
 *
2097
 * ### Attributes:
2098
 *
2099
 * - `empty` - If true, the empty select option is shown. If a string,
2100
 *   that string is displayed as the empty element.
2101
 * - `orderYear` - Ordering of year values in select options.
2102
 *   Possible values 'asc', 'desc'. Default 'desc'
2103
 * - `value` The selected value of the input.
2104
 *
2105
 * @param string $fieldName Prefix name for the SELECT element
2106
 * @param integer $minYear First year in sequence
2107
 * @param integer $maxYear Last year in sequence
2108
 * @param array $attributes Attribute array for the select elements.
2109
 * @return string Completed year select input
2110
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::year
2111
 */
2112
	public function year($fieldName, $minYear = null, $maxYear = null, $attributes = array()) {
2113
		$attributes += array('empty' => true, 'value' => null);
2114
		if ((empty($attributes['value']) || $attributes['value'] === true) && $value = $this->value($fieldName)) {
2115
			if (is_array($value)) {
2116
				$year = null;
2117
				extract($value);
2118
				$attributes['value'] = $year;
2119
			} else {
2120
				if (empty($value)) {
2121
					if (!$attributes['empty'] && !$maxYear) {
2122
						$attributes['value'] = 'now';
2123
 
2124
					} elseif (!$attributes['empty'] && $maxYear && !$attributes['value']) {
2125
						$attributes['value'] = $maxYear;
2126
					}
2127
				} else {
2128
					$attributes['value'] = $value;
2129
				}
2130
			}
2131
		}
2132
 
2133
		if (strlen($attributes['value']) > 4 || $attributes['value'] === 'now') {
2134
			$attributes['value'] = date_create($attributes['value'])->format('Y');
2135
		} elseif ($attributes['value'] === false) {
2136
			$attributes['value'] = null;
2137
		}
2138
		$yearOptions = array('value' => $attributes['value'], 'min' => $minYear, 'max' => $maxYear, 'order' => 'desc');
2139
		if (isset($attributes['orderYear'])) {
2140
			$yearOptions['order'] = $attributes['orderYear'];
2141
			unset($attributes['orderYear']);
2142
		}
2143
		return $this->select(
2144
			$fieldName . '.year', $this->_generateOptions('year', $yearOptions),
2145
			$attributes
2146
		);
2147
	}
2148
 
2149
/**
2150
 * Returns a SELECT element for months.
2151
 *
2152
 * ### Attributes:
2153
 *
2154
 * - `monthNames` - If false, 2 digit numbers will be used instead of text.
2155
 *   If a array, the given array will be used.
2156
 * - `empty` - If true, the empty select option is shown. If a string,
2157
 *   that string is displayed as the empty element.
2158
 * - `value` The selected value of the input.
2159
 *
2160
 * @param string $fieldName Prefix name for the SELECT element
2161
 * @param array $attributes Attributes for the select element
2162
 * @return string A generated month select dropdown.
2163
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::month
2164
 */
2165
	public function month($fieldName, $attributes = array()) {
2166
		$attributes += array('empty' => true, 'value' => null);
2167
		$attributes = $this->_dateTimeSelected('month', $fieldName, $attributes);
2168
 
2169
		if (strlen($attributes['value']) > 2) {
2170
			$attributes['value'] = date_create($attributes['value'])->format('m');
2171
		} elseif ($attributes['value'] === false) {
2172
			$attributes['value'] = null;
2173
		}
2174
		$defaults = array('monthNames' => true);
2175
		$attributes = array_merge($defaults, (array)$attributes);
2176
		$monthNames = $attributes['monthNames'];
2177
		unset($attributes['monthNames']);
2178
 
2179
		return $this->select(
2180
			$fieldName . ".month",
2181
			$this->_generateOptions('month', array('monthNames' => $monthNames)),
2182
			$attributes
2183
		);
2184
	}
2185
 
2186
/**
2187
 * Returns a SELECT element for hours.
2188
 *
2189
 * ### Attributes:
2190
 *
2191
 * - `empty` - If true, the empty select option is shown. If a string,
2192
 *   that string is displayed as the empty element.
2193
 * - `value` The selected value of the input.
2194
 *
2195
 * @param string $fieldName Prefix name for the SELECT element
2196
 * @param boolean $format24Hours True for 24 hours format
2197
 * @param array $attributes List of HTML attributes
2198
 * @return string Completed hour select input
2199
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::hour
2200
 */
2201
	public function hour($fieldName, $format24Hours = false, $attributes = array()) {
2202
		$attributes += array('empty' => true, 'value' => null);
2203
		$attributes = $this->_dateTimeSelected('hour', $fieldName, $attributes);
2204
 
2205
		if (strlen($attributes['value']) > 2) {
2206
			$Date = new DateTime($attributes['value']);
2207
			if ($format24Hours) {
2208
				$attributes['value'] = $Date->format('H');
2209
			} else {
2210
				$attributes['value'] = $Date->format('g');
2211
			}
2212
		} elseif ($attributes['value'] === false) {
2213
			$attributes['value'] = null;
2214
		}
2215
 
2216
		if ($attributes['value'] > 12 && !$format24Hours) {
2217
			$attributes['value'] -= 12;
2218
		}
2219
		if (($attributes['value'] === 0 || $attributes['value'] === '00') && !$format24Hours) {
2220
			$attributes['value'] = 12;
2221
		}
2222
 
2223
		return $this->select(
2224
			$fieldName . ".hour",
2225
			$this->_generateOptions($format24Hours ? 'hour24' : 'hour'),
2226
			$attributes
2227
		);
2228
	}
2229
 
2230
/**
2231
 * Returns a SELECT element for minutes.
2232
 *
2233
 * ### Attributes:
2234
 *
2235
 * - `empty` - If true, the empty select option is shown. If a string,
2236
 *   that string is displayed as the empty element.
2237
 * - `value` The selected value of the input.
2238
 *
2239
 * @param string $fieldName Prefix name for the SELECT element
2240
 * @param array $attributes Array of Attributes
2241
 * @return string Completed minute select input.
2242
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::minute
2243
 */
2244
	public function minute($fieldName, $attributes = array()) {
2245
		$attributes += array('empty' => true, 'value' => null);
2246
		$attributes = $this->_dateTimeSelected('min', $fieldName, $attributes);
2247
 
2248
		if (strlen($attributes['value']) > 2) {
2249
			$attributes['value'] = date_create($attributes['value'])->format('i');
2250
		} elseif ($attributes['value'] === false) {
2251
			$attributes['value'] = null;
2252
		}
2253
		$minuteOptions = array();
2254
 
2255
		if (isset($attributes['interval'])) {
2256
			$minuteOptions['interval'] = $attributes['interval'];
2257
			unset($attributes['interval']);
2258
		}
2259
		return $this->select(
2260
			$fieldName . ".min", $this->_generateOptions('minute', $minuteOptions),
2261
			$attributes
2262
		);
2263
	}
2264
 
2265
/**
2266
 * Selects values for dateTime selects.
2267
 *
2268
 * @param string $select Name of element field. ex. 'day'
2269
 * @param string $fieldName Name of fieldName being generated ex. Model.created
2270
 * @param array $attributes Array of attributes, must contain 'empty' key.
2271
 * @return array Attributes array with currently selected value.
2272
 */
2273
	protected function _dateTimeSelected($select, $fieldName, $attributes) {
2274
		if ((empty($attributes['value']) || $attributes['value'] === true) && $value = $this->value($fieldName)) {
2275
			if (is_array($value)) {
2276
				$attributes['value'] = isset($value[$select]) ? $value[$select] : null;
2277
			} else {
2278
				if (empty($value)) {
2279
					if (!$attributes['empty']) {
2280
						$attributes['value'] = 'now';
2281
					}
2282
				} else {
2283
					$attributes['value'] = $value;
2284
				}
2285
			}
2286
		}
2287
		return $attributes;
2288
	}
2289
 
2290
/**
2291
 * Returns a SELECT element for AM or PM.
2292
 *
2293
 * ### Attributes:
2294
 *
2295
 * - `empty` - If true, the empty select option is shown. If a string,
2296
 *   that string is displayed as the empty element.
2297
 * - `value` The selected value of the input.
2298
 *
2299
 * @param string $fieldName Prefix name for the SELECT element
2300
 * @param array|string $attributes Array of Attributes
2301
 * @return string Completed meridian select input
2302
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::meridian
2303
 */
2304
	public function meridian($fieldName, $attributes = array()) {
2305
		$attributes += array('empty' => true, 'value' => null);
2306
		if ((empty($attributes['value']) || $attributes['value'] === true) && $value = $this->value($fieldName)) {
2307
			if (is_array($value)) {
2308
				$meridian = null;
2309
				extract($value);
2310
				$attributes['value'] = $meridian;
2311
			} else {
2312
				if (empty($value)) {
2313
					if (!$attributes['empty']) {
2314
						$attributes['value'] = date('a');
2315
					}
2316
				} else {
2317
					$attributes['value'] = date_create($attributes['value'])->format('a');
2318
				}
2319
			}
2320
		}
2321
 
2322
		if ($attributes['value'] === false) {
2323
			$attributes['value'] = null;
2324
		}
2325
		return $this->select(
2326
			$fieldName . ".meridian", $this->_generateOptions('meridian'),
2327
			$attributes
2328
		);
2329
	}
2330
 
2331
/**
2332
 * Returns a set of SELECT elements for a full datetime setup: day, month and year, and then time.
2333
 *
2334
 * ### Attributes:
2335
 *
2336
 * - `monthNames` If false, 2 digit numbers will be used instead of text.
2337
 *   If a array, the given array will be used.
2338
 * - `minYear` The lowest year to use in the year select
2339
 * - `maxYear` The maximum year to use in the year select
2340
 * - `interval` The interval for the minutes select. Defaults to 1
2341
 * - `separator` The contents of the string between select elements. Defaults to '-'
2342
 * - `empty` - If true, the empty select option is shown. If a string,
2343
 *   that string is displayed as the empty element.
2344
 * - `round` - Set to `up` or `down` if you want to force rounding in either direction. Defaults to null.
2345
 * - `value` | `default` The default value to be used by the input. A value in `$this->data`
2346
 *   matching the field name will override this value. If no default is provided `time()` will be used.
2347
 *
2348
 * @param string $fieldName Prefix name for the SELECT element
2349
 * @param string $dateFormat DMY, MDY, YMD, or null to not generate date inputs.
2350
 * @param string $timeFormat 12, 24, or null to not generate time inputs.
2351
 * @param array|string $attributes array of Attributes
2352
 * @return string Generated set of select boxes for the date and time formats chosen.
2353
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::dateTime
2354
 */
2355
	public function dateTime($fieldName, $dateFormat = 'DMY', $timeFormat = '12', $attributes = array()) {
2356
		$attributes += array('empty' => true, 'value' => null);
2357
		$year = $month = $day = $hour = $min = $meridian = null;
2358
 
2359
		if (empty($attributes['value'])) {
2360
			$attributes = $this->value($attributes, $fieldName);
2361
		}
2362
 
2363
		if ($attributes['value'] === null && $attributes['empty'] != true) {
2364
			$attributes['value'] = time();
2365
			if (!empty($attributes['maxYear']) && $attributes['maxYear'] < date('Y')) {
2366
				$attributes['value'] = strtotime(date($attributes['maxYear'] . '-m-d'));
2367
			}
2368
		}
2369
 
2370
		if (!empty($attributes['value'])) {
2371
			list($year, $month, $day, $hour, $min, $meridian) = $this->_getDateTimeValue(
2372
				$attributes['value'],
2373
				$timeFormat
2374
			);
2375
		}
2376
 
2377
		$defaults = array(
2378
			'minYear' => null, 'maxYear' => null, 'separator' => '-',
2379
			'interval' => 1, 'monthNames' => true, 'round' => null
2380
		);
2381
		$attributes = array_merge($defaults, (array)$attributes);
2382
		if (isset($attributes['minuteInterval'])) {
2383
			$attributes['interval'] = $attributes['minuteInterval'];
2384
			unset($attributes['minuteInterval']);
2385
		}
2386
		$minYear = $attributes['minYear'];
2387
		$maxYear = $attributes['maxYear'];
2388
		$separator = $attributes['separator'];
2389
		$interval = $attributes['interval'];
2390
		$monthNames = $attributes['monthNames'];
2391
		$round = $attributes['round'];
2392
		$attributes = array_diff_key($attributes, $defaults);
2393
 
2394
		if ($timeFormat == 12 && $hour == 12) {
2395
			$hour = 0;
2396
		}
2397
 
2398
		if (!empty($interval) && $interval > 1 && !empty($min)) {
2399
			$current = new DateTime();
2400
			if ($year !== null) {
2401
				$current->setDate($year, $month, $day);
2402
			}
2403
			if ($hour !== null) {
2404
				$current->setTime($hour, $min);
2405
			}
2406
			$changeValue = $min * (1 / $interval);
2407
			switch ($round) {
2408
				case 'up':
2409
					$changeValue = ceil($changeValue);
2410
					break;
2411
				case 'down':
2412
					$changeValue = floor($changeValue);
2413
					break;
2414
				default:
2415
					$changeValue = round($changeValue);
2416
			}
2417
			$change = ($changeValue * $interval) - $min;
2418
			$current->modify($change > 0 ? "+$change minutes" : "$change minutes");
2419
			$format = ($timeFormat == 12) ? 'Y m d h i a' : 'Y m d H i a';
2420
			$newTime = explode(' ', $current->format($format));
2421
			list($year, $month, $day, $hour, $min, $meridian) = $newTime;
2422
		}
2423
 
2424
		$keys = array('Day', 'Month', 'Year', 'Hour', 'Minute', 'Meridian');
2425
		$attrs = array_fill_keys($keys, $attributes);
2426
 
2427
		$hasId = isset($attributes['id']);
2428
		if ($hasId && is_array($attributes['id'])) {
2429
			// check for missing ones and build selectAttr for each element
2430
			$attributes['id'] += array(
2431
				'month' => '',
2432
				'year' => '',
2433
				'day' => '',
2434
				'hour' => '',
2435
				'minute' => '',
2436
				'meridian' => ''
2437
			);
2438
			foreach ($keys as $key) {
2439
				$attrs[$key]['id'] = $attributes['id'][strtolower($key)];
2440
			}
2441
		}
2442
		if ($hasId && is_string($attributes['id'])) {
2443
			// build out an array version
2444
			foreach ($keys as $key) {
2445
				$attrs[$key]['id'] = $attributes['id'] . $key;
2446
			}
2447
		}
2448
 
2449
		if (is_array($attributes['empty'])) {
2450
			$attributes['empty'] += array(
2451
				'month' => true,
2452
				'year' => true,
2453
				'day' => true,
2454
				'hour' => true,
2455
				'minute' => true,
2456
				'meridian' => true
2457
			);
2458
			foreach ($keys as $key) {
2459
				$attrs[$key]['empty'] = $attributes['empty'][strtolower($key)];
2460
			}
2461
		}
2462
 
2463
		$selects = array();
2464
		foreach (preg_split('//', $dateFormat, -1, PREG_SPLIT_NO_EMPTY) as $char) {
2465
			switch ($char) {
2466
				case 'Y':
2467
					$attrs['Year']['value'] = $year;
2468
					$selects[] = $this->year(
2469
						$fieldName, $minYear, $maxYear, $attrs['Year']
2470
					);
2471
					break;
2472
				case 'M':
2473
					$attrs['Month']['value'] = $month;
2474
					$attrs['Month']['monthNames'] = $monthNames;
2475
					$selects[] = $this->month($fieldName, $attrs['Month']);
2476
					break;
2477
				case 'D':
2478
					$attrs['Day']['value'] = $day;
2479
					$selects[] = $this->day($fieldName, $attrs['Day']);
2480
					break;
2481
			}
2482
		}
2483
		$opt = implode($separator, $selects);
2484
 
2485
		$attrs['Minute']['interval'] = $interval;
2486
		switch ($timeFormat) {
2487
			case '24':
2488
				$attrs['Hour']['value'] = $hour;
2489
				$attrs['Minute']['value'] = $min;
2490
				$opt .= $this->hour($fieldName, true, $attrs['Hour']) . ':' .
2491
				$this->minute($fieldName, $attrs['Minute']);
2492
				break;
2493
			case '12':
2494
				$attrs['Hour']['value'] = $hour;
2495
				$attrs['Minute']['value'] = $min;
2496
				$attrs['Meridian']['value'] = $meridian;
2497
				$opt .= $this->hour($fieldName, false, $attrs['Hour']) . ':' .
2498
				$this->minute($fieldName, $attrs['Minute']) . ' ' .
2499
				$this->meridian($fieldName, $attrs['Meridian']);
2500
				break;
2501
		}
2502
		return $opt;
2503
	}
2504
 
2505
/**
2506
 * Parse the value for a datetime selected value
2507
 *
2508
 * @param string|array $value The selected value.
2509
 * @param integer $timeFormat The time format
2510
 * @return array Array of selected value.
2511
 */
2512
	protected function _getDateTimeValue($value, $timeFormat) {
2513
		$year = $month = $day = $hour = $min = $meridian = null;
2514
		if (is_array($value)) {
2515
			extract($value);
2516
			if ($meridian === 'pm') {
2517
				$hour += 12;
2518
			}
2519
			return array($year, $month, $day, $hour, $min, $meridian);
2520
		}
2521
 
2522
		if (is_numeric($value)) {
2523
			$value = strftime('%Y-%m-%d %H:%M:%S', $value);
2524
		}
2525
		$meridian = 'am';
2526
		$pos = strpos($value, '-');
2527
		if ($pos !== false) {
2528
			$date = explode('-', $value);
2529
			$days = explode(' ', $date[2]);
2530
			$day = $days[0];
2531
			$month = $date[1];
2532
			$year = $date[0];
2533
		} else {
2534
			$days[1] = $value;
2535
		}
2536
 
2537
		if (!empty($timeFormat)) {
2538
			$time = explode(':', $days[1]);
2539
 
2540
			if ($time[0] >= 12) {
2541
				$meridian = 'pm';
2542
			}
2543
			$hour = $min = null;
2544
			if (isset($time[1])) {
2545
				$hour = $time[0];
2546
				$min = $time[1];
2547
			}
2548
		}
2549
		return array($year, $month, $day, $hour, $min, $meridian);
2550
	}
2551
 
2552
/**
2553
 * Gets the input field name for the current tag
2554
 *
2555
 * @param array $options
2556
 * @param string $field
2557
 * @param string $key
2558
 * @return array
2559
 */
2560
	protected function _name($options = array(), $field = null, $key = 'name') {
2561
		if ($this->requestType === 'get') {
2562
			if ($options === null) {
2563
				$options = array();
2564
			} elseif (is_string($options)) {
2565
				$field = $options;
2566
				$options = 0;
2567
			}
2568
 
2569
			if (!empty($field)) {
2570
				$this->setEntity($field);
2571
			}
2572
 
2573
			if (is_array($options) && isset($options[$key])) {
2574
				return $options;
2575
			}
2576
 
2577
			$entity = $this->entity();
2578
			$model = $this->model();
2579
			$name = $model === $entity[0] && isset($entity[1]) ? $entity[1] : $entity[0];
2580
			$last = $entity[count($entity) - 1];
2581
			if (in_array($last, $this->_fieldSuffixes)) {
2582
				$name .= '[' . $last . ']';
2583
			}
2584
 
2585
			if (is_array($options)) {
2586
				$options[$key] = $name;
2587
				return $options;
2588
			}
2589
			return $name;
2590
		}
2591
		return parent::_name($options, $field, $key);
2592
	}
2593
 
2594
/**
2595
 * Returns an array of formatted OPTION/OPTGROUP elements
2596
 *
2597
 * @param array $elements
2598
 * @param array $parents
2599
 * @param boolean $showParents
2600
 * @param array $attributes
2601
 * @return array
2602
 */
2603
	protected function _selectOptions($elements = array(), $parents = array(), $showParents = null, $attributes = array()) {
2604
		$select = array();
2605
		$attributes = array_merge(
2606
			array('escape' => true, 'style' => null, 'value' => null, 'class' => null),
2607
			$attributes
2608
		);
2609
		$selectedIsEmpty = ($attributes['value'] === '' || $attributes['value'] === null);
2610
		$selectedIsArray = is_array($attributes['value']);
2611
 
2612
		foreach ($elements as $name => $title) {
2613
			$htmlOptions = array();
2614
			if (is_array($title) && (!isset($title['name']) || !isset($title['value']))) {
2615
				if (!empty($name)) {
2616
					if ($attributes['style'] === 'checkbox') {
2617
						$select[] = $this->Html->useTag('fieldsetend');
2618
					} else {
2619
						$select[] = $this->Html->useTag('optiongroupend');
2620
					}
2621
					$parents[] = $name;
2622
				}
2623
				$select = array_merge($select, $this->_selectOptions(
2624
					$title, $parents, $showParents, $attributes
2625
				));
2626
 
2627
				if (!empty($name)) {
2628
					$name = $attributes['escape'] ? h($name) : $name;
2629
					if ($attributes['style'] === 'checkbox') {
2630
						$select[] = $this->Html->useTag('fieldsetstart', $name);
2631
					} else {
2632
						$select[] = $this->Html->useTag('optiongroup', $name, '');
2633
					}
2634
				}
2635
				$name = null;
2636
			} elseif (is_array($title)) {
2637
				$htmlOptions = $title;
2638
				$name = $title['value'];
2639
				$title = $title['name'];
2640
				unset($htmlOptions['name'], $htmlOptions['value']);
2641
			}
2642
 
2643
			if ($name !== null) {
2644
				$isNumeric = is_numeric($name);
2645
				if (
2646
					(!$selectedIsArray && !$selectedIsEmpty && (string)$attributes['value'] == (string)$name) ||
2647
					($selectedIsArray && in_array((string)$name, $attributes['value'], !$isNumeric))
2648
				) {
2649
					if ($attributes['style'] === 'checkbox') {
2650
						$htmlOptions['checked'] = true;
2651
					} else {
2652
						$htmlOptions['selected'] = 'selected';
2653
					}
2654
				}
2655
 
2656
				if ($showParents || (!in_array($title, $parents))) {
2657
					$title = ($attributes['escape']) ? h($title) : $title;
2658
 
2659
					$hasDisabled = !empty($attributes['disabled']);
2660
					if ($hasDisabled) {
2661
						$disabledIsArray = is_array($attributes['disabled']);
2662
						if ($disabledIsArray) {
2663
							$disabledIsNumeric = is_numeric($name);
2664
						}
2665
					}
2666
					if (
2667
						$hasDisabled &&
2668
						$disabledIsArray &&
2669
						in_array((string)$name, $attributes['disabled'], !$disabledIsNumeric)
2670
					) {
2671
						$htmlOptions['disabled'] = 'disabled';
2672
					}
2673
					if ($hasDisabled && !$disabledIsArray && $attributes['style'] === 'checkbox') {
2674
						$htmlOptions['disabled'] = $attributes['disabled'] === true ? 'disabled' : $attributes['disabled'];
2675
					}
2676
 
2677
					if ($attributes['style'] === 'checkbox') {
2678
						$htmlOptions['value'] = $name;
2679
 
2680
						$tagName = $attributes['id'] . Inflector::camelize(Inflector::slug($name));
2681
						$htmlOptions['id'] = $tagName;
2682
						$label = array('for' => $tagName);
2683
 
2684
						if (isset($htmlOptions['checked']) && $htmlOptions['checked'] === true) {
2685
							$label['class'] = 'selected';
2686
						}
2687
 
2688
						$name = $attributes['name'];
2689
 
2690
						if (empty($attributes['class'])) {
2691
							$attributes['class'] = 'checkbox';
2692
						} elseif ($attributes['class'] === 'form-error') {
2693
							$attributes['class'] = 'checkbox ' . $attributes['class'];
2694
						}
2695
						$label = $this->label(null, $title, $label);
2696
						$item = $this->Html->useTag('checkboxmultiple', $name, $htmlOptions);
2697
						$select[] = $this->Html->div($attributes['class'], $item . $label);
2698
					} else {
2699
						$select[] = $this->Html->useTag('selectoption', $name, $htmlOptions, $title);
2700
					}
2701
				}
2702
			}
2703
		}
2704
 
2705
		return array_reverse($select, true);
2706
	}
2707
 
2708
/**
2709
 * Generates option lists for common <select /> menus
2710
 *
2711
 * @param string $name
2712
 * @param array $options
2713
 * @return array
2714
 */
2715
	protected function _generateOptions($name, $options = array()) {
2716
		if (!empty($this->options[$name])) {
2717
			return $this->options[$name];
2718
		}
2719
		$data = array();
2720
 
2721
		switch ($name) {
2722
			case 'minute':
2723
				if (isset($options['interval'])) {
2724
					$interval = $options['interval'];
2725
				} else {
2726
					$interval = 1;
2727
				}
2728
				$i = 0;
2729
				while ($i < 60) {
2730
					$data[sprintf('%02d', $i)] = sprintf('%02d', $i);
2731
					$i += $interval;
2732
				}
2733
				break;
2734
			case 'hour':
2735
				for ($i = 1; $i <= 12; $i++) {
2736
					$data[sprintf('%02d', $i)] = $i;
2737
				}
2738
				break;
2739
			case 'hour24':
2740
				for ($i = 0; $i <= 23; $i++) {
2741
					$data[sprintf('%02d', $i)] = $i;
2742
				}
2743
				break;
2744
			case 'meridian':
2745
				$data = array('am' => 'am', 'pm' => 'pm');
2746
				break;
2747
			case 'day':
2748
				$min = 1;
2749
				$max = 31;
2750
 
2751
				if (isset($options['min'])) {
2752
					$min = $options['min'];
2753
				}
2754
				if (isset($options['max'])) {
2755
					$max = $options['max'];
2756
				}
2757
 
2758
				for ($i = $min; $i <= $max; $i++) {
2759
					$data[sprintf('%02d', $i)] = $i;
2760
				}
2761
				break;
2762
			case 'month':
2763
				if ($options['monthNames'] === true) {
2764
					$data['01'] = __d('cake', 'January');
2765
					$data['02'] = __d('cake', 'February');
2766
					$data['03'] = __d('cake', 'March');
2767
					$data['04'] = __d('cake', 'April');
2768
					$data['05'] = __d('cake', 'May');
2769
					$data['06'] = __d('cake', 'June');
2770
					$data['07'] = __d('cake', 'July');
2771
					$data['08'] = __d('cake', 'August');
2772
					$data['09'] = __d('cake', 'September');
2773
					$data['10'] = __d('cake', 'October');
2774
					$data['11'] = __d('cake', 'November');
2775
					$data['12'] = __d('cake', 'December');
2776
				} elseif (is_array($options['monthNames'])) {
2777
					$data = $options['monthNames'];
2778
				} else {
2779
					for ($m = 1; $m <= 12; $m++) {
2780
						$data[sprintf("%02s", $m)] = strftime("%m", mktime(1, 1, 1, $m, 1, 1999));
2781
					}
2782
				}
2783
				break;
2784
			case 'year':
2785
				$current = intval(date('Y'));
2786
 
2787
				$min = !isset($options['min']) ? $current - 20 : (int)$options['min'];
2788
				$max = !isset($options['max']) ? $current + 20 : (int)$options['max'];
2789
 
2790
				if ($min > $max) {
2791
					list($min, $max) = array($max, $min);
2792
				}
2793
				if (!empty($options['value']) && (int)$options['value'] < $min) {
2794
					$min = (int)$options['value'];
2795
				} elseif (!empty($options['value']) && (int)$options['value'] > $max) {
2796
					$max = (int)$options['value'];
2797
				}
2798
 
2799
				for ($i = $min; $i <= $max; $i++) {
2800
					$data[$i] = $i;
2801
				}
2802
				if ($options['order'] !== 'asc') {
2803
					$data = array_reverse($data, true);
2804
				}
2805
				break;
2806
		}
2807
		$this->_options[$name] = $data;
2808
		return $this->_options[$name];
2809
	}
2810
 
2811
/**
2812
 * Sets field defaults and adds field to form security input hash.
2813
 * Will also add a 'form-error' class if the field contains validation errors.
2814
 *
2815
 * ### Options
2816
 *
2817
 * - `secure` - boolean whether or not the field should be added to the security fields.
2818
 *   Disabling the field using the `disabled` option, will also omit the field from being
2819
 *   part of the hashed key.
2820
 *
2821
 * This method will convert a numerically indexed 'disabled' into a associative
2822
 * value. FormHelper's internals expect associative options.
2823
 *
2824
 * @param string $field Name of the field to initialize options for.
2825
 * @param array $options Array of options to append options into.
2826
 * @return array Array of options for the input.
2827
 */
2828
	protected function _initInputField($field, $options = array()) {
2829
		if (isset($options['secure'])) {
2830
			$secure = $options['secure'];
2831
			unset($options['secure']);
2832
		} else {
2833
			$secure = (isset($this->request['_Token']) && !empty($this->request['_Token']));
2834
		}
2835
 
2836
		$disabledIndex = array_search('disabled', $options, true);
2837
		if (is_int($disabledIndex)) {
2838
			unset($options[$disabledIndex]);
2839
			$options['disabled'] = true;
2840
		}
2841
 
2842
		$result = parent::_initInputField($field, $options);
2843
		if ($this->tagIsInvalid() !== false) {
2844
			$result = $this->addClass($result, 'form-error');
2845
		}
2846
 
2847
		if (!empty($result['disabled'])) {
2848
			return $result;
2849
		}
2850
 
2851
		if (!isset($result['required']) &&
2852
			$this->_introspectModel($this->model(), 'validates', $this->field())
2853
		) {
2854
			$result['required'] = true;
2855
		}
2856
 
2857
		if ($secure === self::SECURE_SKIP) {
2858
			return $result;
2859
		}
2860
 
2861
		$this->_secure($secure, $this->_secureFieldName($options));
2862
		return $result;
2863
	}
2864
 
2865
/**
2866
 * Get the field name for use with _secure().
2867
 *
2868
 * Parses the name attribute to create a dot separated name value for use
2869
 * in secured field hash.
2870
 *
2871
 * @param array $options An array of options possibly containing a name key.
2872
 * @return string|null
2873
 */
2874
	protected function _secureFieldName($options) {
2875
		if (isset($options['name'])) {
2876
			preg_match_all('/\[(.*?)\]/', $options['name'], $matches);
2877
			if (isset($matches[1])) {
2878
				return $matches[1];
2879
			}
2880
		}
2881
		return null;
2882
	}
2883
 
2884
/**
2885
 * Set/Get inputDefaults for form elements
2886
 *
2887
 * @param array $defaults New default values
2888
 * @param boolean Merge with current defaults
2889
 * @return array inputDefaults
2890
 */
2891
	public function inputDefaults($defaults = null, $merge = false) {
2892
		if ($defaults !== null) {
2893
			if ($merge) {
2894
				$this->_inputDefaults = array_merge($this->_inputDefaults, (array)$defaults);
2895
			} else {
2896
				$this->_inputDefaults = (array)$defaults;
2897
			}
2898
		}
2899
		return $this->_inputDefaults;
2900
	}
2901
 
2902
}