FormHelper::radio()   F
last analyzed

Complexity

Conditions 29
Paths > 20000

Size

Total Lines 107
Code Lines 71

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 29
eloc 71
c 0
b 0
f 0
nc 518400
nop 3
dl 0
loc 107
rs 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
App::uses('Inflector', 'Utility');
21
22
/**
23
 * Form helper library.
24
 *
25
 * Automatic generation of HTML FORMs from given data.
26
 *
27
 * @package       Cake.View.Helper
28
 * @property      HtmlHelper $Html
29
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html
30
 */
31
class FormHelper extends AppHelper {
32
33
/**
34
 * Other helpers used by FormHelper
35
 *
36
 * @var array
37
 */
38
	public $helpers = array('Html');
39
40
/**
41
 * Options used by DateTime fields
42
 *
43
 * @var array
44
 */
45
	protected $_options = array(
46
		'day' => array(), 'minute' => array(), 'hour' => array(),
47
		'month' => array(), 'year' => array(), 'meridian' => array()
48
	);
49
50
/**
51
 * List of fields created, used with secure forms.
52
 *
53
 * @var array
54
 */
55
	public $fields = array();
56
57
/**
58
 * Constant used internally to skip the securing process,
59
 * and neither add the field to the hash or to the unlocked fields.
60
 *
61
 * @var string
62
 */
63
	const SECURE_SKIP = 'skip';
64
65
/**
66
 * Defines the type of form being created. Set by FormHelper::create().
67
 *
68
 * @var string
69
 */
70
	public $requestType = null;
71
72
/**
73
 * The default model being used for the current form.
74
 *
75
 * @var string
76
 */
77
	public $defaultModel = null;
78
79
/**
80
 * Persistent default options used by input(). Set by FormHelper::create().
81
 *
82
 * @var array
83
 */
84
	protected $_inputDefaults = array();
85
86
/**
87
 * An array of field names that have been excluded from
88
 * the Token hash used by SecurityComponent's validatePost method
89
 *
90
 * @see FormHelper::_secure()
91
 * @see SecurityComponent::validatePost()
92
 * @var array
93
 */
94
	protected $_unlockedFields = array();
95
96
/**
97
 * Holds the model references already loaded by this helper
98
 * product of trying to inspect them out of field names
99
 *
100
 * @var array
101
 */
102
	protected $_models = array();
103
104
/**
105
 * Holds all the validation errors for models loaded and inspected
106
 * it can also be set manually to be able to display custom error messages
107
 * in the any of the input fields generated by this helper
108
 *
109
 * @var array
110
 */
111
	public $validationErrors = array();
112
113
/**
114
 * Holds already used DOM ID suffixes to avoid collisions with multiple form field elements.
115
 *
116
 * @var array
117
 */
118
	protected $_domIdSuffixes = array();
119
120
/**
121
 * The action attribute value of the last created form.
122
 * Used to make form/request specific hashes for SecurityComponent.
123
 *
124
 * @var string
125
 */
126
	protected $_lastAction = '';
127
128
/**
129
 * Copies the validationErrors variable from the View object into this instance
130
 *
131
 * @param View $View The View this helper is being attached to.
132
 * @param array $settings Configuration settings for the helper.
133
 */
134
	public function __construct(View $View, $settings = array()) {
135
		parent::__construct($View, $settings);
136
		$this->validationErrors =& $View->validationErrors;
137
	}
138
139
/**
140
 * Guess the location for a model based on its name and tries to create a new instance
141
 * or get an already created instance of the model
142
 *
143
 * @param string $model
144
 * @return Model model instance
145
 */
146
	protected function _getModel($model) {
147
		$object = null;
148
		if (!$model || $model === 'Model') {
149
			return $object;
150
		}
151
152
		if (array_key_exists($model, $this->_models)) {
153
			return $this->_models[$model];
154
		}
155
156
		if (ClassRegistry::isKeySet($model)) {
157
			$object = ClassRegistry::getObject($model);
158
		} elseif (isset($this->request->params['models'][$model])) {
159
			$plugin = $this->request->params['models'][$model]['plugin'];
160
			$plugin .= ($plugin) ? '.' : null;
161
			$object = ClassRegistry::init(array(
162
				'class' => $plugin . $this->request->params['models'][$model]['className'],
163
				'alias' => $model
164
			));
165
		} elseif (ClassRegistry::isKeySet($this->defaultModel)) {
166
			$defaultObject = ClassRegistry::getObject($this->defaultModel);
167
			if ($defaultObject && in_array($model, array_keys($defaultObject->getAssociated()), true) && isset($defaultObject->{$model})) {
168
				$object = $defaultObject->{$model};
169
			}
170
		} else {
171
			$object = ClassRegistry::init($model, true);
172
		}
173
174
		$this->_models[$model] = $object;
175
		if (!$object) {
176
			return null;
177
		}
178
179
		$this->fieldset[$model] = array('fields' => null, 'key' => $object->primaryKey, 'validates' => null);
180
		return $object;
181
	}
182
183
/**
184
 * Inspects the model properties to extract information from them.
185
 * Currently it can extract information from the the fields, the primary key and required fields
186
 *
187
 * The $key parameter accepts the following list of values:
188
 *
189
 * - key: Returns the name of the primary key for the model
190
 * - fields: Returns the model schema
191
 * - validates: returns the list of fields that are required
192
 * - errors: returns the list of validation errors
193
 *
194
 * If the $field parameter is passed if will return the information for that sole field.
195
 *
196
 * `$this->_introspectModel('Post', 'fields', 'title');` will return the schema information for title column
197
 *
198
 * @param string $model name of the model to extract information from
199
 * @param string $key name of the special information key to obtain (key, fields, validates, errors)
200
 * @param string $field name of the model field to get information from
201
 * @return mixed information extracted for the special key and field in a model
202
 */
203
	protected function _introspectModel($model, $key, $field = null) {
204
		$object = $this->_getModel($model);
205
		if (!$object) {
206
			return;
207
		}
208
209
		if ($key === 'key') {
210
			return $this->fieldset[$model]['key'] = $object->primaryKey;
211
		}
212
213
		if ($key === 'fields') {
214
			if (!isset($this->fieldset[$model]['fields'])) {
215
				$this->fieldset[$model]['fields'] = $object->schema();
216
				foreach ($object->hasAndBelongsToMany as $alias => $assocData) {
217
					$this->fieldset[$object->alias]['fields'][$alias] = array('type' => 'multiple');
218
				}
219
			}
220
			if ($field === null || $field === false) {
221
				return $this->fieldset[$model]['fields'];
222
			} elseif (isset($this->fieldset[$model]['fields'][$field])) {
223
				return $this->fieldset[$model]['fields'][$field];
224
			}
225
			return isset($object->hasAndBelongsToMany[$field]) ? array('type' => 'multiple') : null;
226
		}
227
228
		if ($key === 'errors' && !isset($this->validationErrors[$model])) {
229
			$this->validationErrors[$model] =& $object->validationErrors;
230
			return $this->validationErrors[$model];
231
		} elseif ($key === 'errors' && isset($this->validationErrors[$model])) {
232
			return $this->validationErrors[$model];
233
		}
234
235
		if ($key === 'validates' && !isset($this->fieldset[$model]['validates'])) {
236
			$validates = array();
237
			foreach (iterator_to_array($object->validator(), true) as $validateField => $validateProperties) {
238
				if ($this->_isRequiredField($validateProperties)) {
239
					$validates[$validateField] = true;
240
				}
241
			}
242
			$this->fieldset[$model]['validates'] = $validates;
243
		}
244
245
		if ($key === 'validates') {
246
			if (empty($field)) {
247
				return $this->fieldset[$model]['validates'];
248
			}
249
			return isset($this->fieldset[$model]['validates'][$field]) ?
250
				$this->fieldset[$model]['validates'] : null;
251
		}
252
	}
253
254
/**
255
 * Returns if a field is required to be filled based on validation properties from the validating object.
256
 *
257
 * @param CakeValidationSet $validationRules
258
 * @return boolean true if field is required to be filled, false otherwise
259
 */
260
	protected function _isRequiredField($validationRules) {
261
		if (empty($validationRules) || count($validationRules) === 0) {
262
			return false;
263
		}
264
265
		$isUpdate = $this->requestType === 'put';
266
		foreach ($validationRules as $rule) {
267
			$rule->isUpdate($isUpdate);
268
			if ($rule->skip()) {
269
				continue;
270
			}
271
272
			return !$rule->allowEmpty;
273
		}
274
		return false;
275
	}
276
277
/**
278
 * Returns false if given form field described by the current entity has no errors.
279
 * Otherwise it returns the validation message
280
 *
281
 * @return mixed Either false when there are no errors, or an array of error
282
 *    strings. An error string could be ''.
283
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::tagIsInvalid
284
 */
285
	public function tagIsInvalid() {
286
		$entity = $this->entity();
287
		$model = array_shift($entity);
288
289
		// 0.Model.field. Fudge entity path
290
		if (empty($model) || is_numeric($model)) {
291
			array_splice($entity, 1, 0, $model);
292
			$model = array_shift($entity);
293
		}
294
295
		$errors = array();
296
		if (!empty($entity) && isset($this->validationErrors[$model])) {
297
			$errors = $this->validationErrors[$model];
298
		}
299
		if (!empty($entity) && empty($errors)) {
300
			$errors = $this->_introspectModel($model, 'errors');
301
		}
302
		if (empty($errors)) {
303
			return false;
304
		}
305
		$errors = Hash::get($errors, implode('.', $entity));
306
		return $errors === null ? false : $errors;
307
	}
308
309
/**
310
 * Returns an HTML FORM element.
311
 *
312
 * ### Options:
313
 *
314
 * - `type` Form method defaults to POST
315
 * - `action`  The controller action the form submits to, (optional).
316
 * - `url`  The URL the form submits to. Can be a string or a URL array. If you use 'url'
317
 *    you should leave 'action' undefined.
318
 * - `default`  Allows for the creation of Ajax forms. Set this to false to prevent the default event handler.
319
 *   Will create an onsubmit attribute if it doesn't not exist. If it does, default action suppression
320
 *   will be appended.
321
 * - `onsubmit` Used in conjunction with 'default' to create ajax forms.
322
 * - `inputDefaults` set the default $options for FormHelper::input(). Any options that would
323
 *   be set when using FormHelper::input() can be set here. Options set with `inputDefaults`
324
 *   can be overridden when calling input()
325
 * - `encoding` Set the accept-charset encoding for the form. Defaults to `Configure::read('App.encoding')`
326
 *
327
 * @param mixed $model The model name for which the form is being defined. Should
328
 *   include the plugin name for plugin models. e.g. `ContactManager.Contact`.
329
 *   If an array is passed and $options argument is empty, the array will be used as options.
330
 *   If `false` no model is used.
331
 * @param array $options An array of html attributes and options.
332
 * @return string An formatted opening FORM tag.
333
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#options-for-create
334
 */
335
	public function create($model = null, $options = array()) {
336
		$created = $id = false;
337
		$append = '';
338
339
		if (is_array($model) && empty($options)) {
340
			$options = $model;
341
			$model = null;
342
		}
343
344
		if (empty($model) && $model !== false && !empty($this->request->params['models'])) {
345
			$model = key($this->request->params['models']);
346
		} elseif (empty($model) && empty($this->request->params['models'])) {
347
			$model = false;
348
		}
349
		$this->defaultModel = $model;
350
351
		$key = null;
352
		if ($model !== false) {
353
			list($plugin, $model) = pluginSplit($model, true);
354
			$key = $this->_introspectModel($plugin . $model, 'key');
355
			$this->setEntity($model, true);
356
		}
357
358
		if ($model !== false && $key) {
359
			$recordExists = (
360
				isset($this->request->data[$model]) &&
361
				!empty($this->request->data[$model][$key]) &&
362
				!is_array($this->request->data[$model][$key])
363
			);
364
365
			if ($recordExists) {
366
				$created = true;
367
				$id = $this->request->data[$model][$key];
368
			}
369
		}
370
371
		$options = array_merge(array(
372
			'type' => ($created && empty($options['action'])) ? 'put' : 'post',
373
			'action' => null,
374
			'url' => null,
375
			'default' => true,
376
			'encoding' => strtolower(Configure::read('App.encoding')),
377
			'inputDefaults' => array()),
378
		$options);
379
		$this->inputDefaults($options['inputDefaults']);
380
		unset($options['inputDefaults']);
381
382
		if (!isset($options['id'])) {
383
			$domId = isset($options['action']) ? $options['action'] : $this->request['action'];
384
			$options['id'] = $this->domId($domId . 'Form');
385
		}
386
387
		if ($options['action'] === null && $options['url'] === null) {
388
			$options['action'] = $this->request->here(false);
389
		} elseif (empty($options['url']) || is_array($options['url'])) {
390
			if (empty($options['url']['controller'])) {
391
				if (!empty($model)) {
392
					$options['url']['controller'] = Inflector::underscore(Inflector::pluralize($model));
393
				} elseif (!empty($this->request->params['controller'])) {
394
					$options['url']['controller'] = Inflector::underscore($this->request->params['controller']);
395
				}
396
			}
397
			if (empty($options['action'])) {
398
				$options['action'] = $this->request->params['action'];
399
			}
400
401
			$plugin = null;
402
			if ($this->plugin) {
403
				$plugin = Inflector::underscore($this->plugin);
404
			}
405
			$actionDefaults = array(
406
				'plugin' => $plugin,
407
				'controller' => $this->_View->viewPath,
408
				'action' => $options['action'],
409
			);
410
			$options['action'] = array_merge($actionDefaults, (array)$options['url']);
411 View Code Duplication
			if (empty($options['action'][0]) && !empty($id)) {
412
				$options['action'][0] = $id;
413
			}
414
		} elseif (is_string($options['url'])) {
415
			$options['action'] = $options['url'];
416
		}
417
		unset($options['url']);
418
419
		switch (strtolower($options['type'])) {
420
			case 'get':
421
				$htmlAttributes['method'] = 'get';
0 ignored issues
show
Coding Style Comprehensibility introduced by
$htmlAttributes was never initialized. Although not strictly required by PHP, it is generally a good practice to add $htmlAttributes = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
422
				break;
423
			case 'file':
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
424
				$htmlAttributes['enctype'] = 'multipart/form-data';
0 ignored issues
show
Coding Style Comprehensibility introduced by
$htmlAttributes was never initialized. Although not strictly required by PHP, it is generally a good practice to add $htmlAttributes = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
425
				$options['type'] = ($created) ? 'put' : 'post';
426
			case 'post':
427
			case 'put':
428
			case 'delete':
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
429
				$append .= $this->hidden('_method', array(
430
					'name' => '_method', 'value' => strtoupper($options['type']), 'id' => null,
431
					'secure' => self::SECURE_SKIP
432
				));
433
			default:
434
				$htmlAttributes['method'] = 'post';
0 ignored issues
show
Bug introduced by
The variable $htmlAttributes does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
435
		}
436
		$this->requestType = strtolower($options['type']);
437
438
		$action = $this->url($options['action']);
439
		unset($options['type'], $options['action']);
440
441
		if (!$options['default']) {
442
			if (!isset($options['onsubmit'])) {
443
				$options['onsubmit'] = '';
444
			}
445
			$htmlAttributes['onsubmit'] = $options['onsubmit'] . 'event.returnValue = false; return false;';
446
		}
447
		unset($options['default']);
448
449
		if (!empty($options['encoding'])) {
450
			$htmlAttributes['accept-charset'] = $options['encoding'];
451
			unset($options['encoding']);
452
		}
453
454
		$htmlAttributes = array_merge($options, $htmlAttributes);
455
456
		$this->fields = array();
457
		if ($this->requestType !== 'get') {
458
			$append .= $this->_csrfField();
459
		}
460
461
		if (!empty($append)) {
462
			$append = $this->Html->useTag('hiddenblock', $append);
463
		}
464
465
		if ($model !== false) {
466
			$this->setEntity($model, true);
467
			$this->_introspectModel($model, 'fields');
468
		}
469
		$this->_lastAction = $action;
0 ignored issues
show
Documentation Bug introduced by
It seems like $action can also be of type array or boolean. However, the property $_lastAction is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
470
		return $this->Html->useTag('form', $action, $htmlAttributes) . $append;
471
	}
472
473
/**
474
 * Return a CSRF input if the _Token is present.
475
 * Used to secure forms in conjunction with SecurityComponent
476
 *
477
 * @return string
478
 */
479
	protected function _csrfField() {
480
		if (empty($this->request->params['_Token'])) {
481
			return '';
482
		}
483
		if (!empty($this->request['_Token']['unlockedFields'])) {
484
			foreach ((array)$this->request['_Token']['unlockedFields'] as $unlocked) {
485
				$this->_unlockedFields[] = $unlocked;
486
			}
487
		}
488
		return $this->hidden('_Token.key', array(
489
			'value' => $this->request->params['_Token']['key'], 'id' => 'Token' . mt_rand(),
490
			'secure' => self::SECURE_SKIP
491
		));
492
	}
493
494
/**
495
 * Closes an HTML form, cleans up values set by FormHelper::create(), and writes hidden
496
 * input fields where appropriate.
497
 *
498
 * If $options is set a form submit button will be created. Options can be either a string or an array.
499
 *
500
 * {{{
501
 * array usage:
502
 *
503
 * array('label' => 'save'); value="save"
504
 * array('label' => 'save', 'name' => 'Whatever'); value="save" name="Whatever"
505
 * array('name' => 'Whatever'); value="Submit" name="Whatever"
506
 * array('label' => 'save', 'name' => 'Whatever', 'div' => 'good') <div class="good"> value="save" name="Whatever"
507
 * array('label' => 'save', 'name' => 'Whatever', 'div' => array('class' => 'good')); <div class="good"> value="save" name="Whatever"
508
 * }}}
509
 *
510
 * @param string|array $options as a string will use $options as the value of button,
511
 * @return string a closing FORM tag optional submit button.
512
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#closing-the-form
513
 */
514
	public function end($options = null) {
515
		$out = null;
516
		$submit = null;
517
518
		if ($options !== null) {
519
			$submitOptions = array();
520
			if (is_string($options)) {
521
				$submit = $options;
522
			} else {
523
				if (isset($options['label'])) {
524
					$submit = $options['label'];
525
					unset($options['label']);
526
				}
527
				$submitOptions = $options;
528
			}
529
			$out .= $this->submit($submit, $submitOptions);
530
		}
531
		if (
532
			$this->requestType !== 'get' &&
533
			isset($this->request['_Token']) &&
534
			!empty($this->request['_Token'])
535
		) {
536
			$out .= $this->secure($this->fields);
537
			$this->fields = array();
538
		}
539
		$this->setEntity(null);
540
		$out .= $this->Html->useTag('formend');
541
542
		$this->_View->modelScope = false;
0 ignored issues
show
Documentation introduced by
The property modelScope does not exist on object<View>. Since you implemented __set, maybe consider adding a @property annotation.

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

<?php

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

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

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

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

}

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

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

See also the PhpDoc documentation for @property.

Loading history...
543
		$this->requestType = null;
544
		return $out;
545
	}
546
547
/**
548
 * Generates a hidden field with a security hash based on the fields used in the form.
549
 *
550
 * @param array $fields The list of fields to use when generating the hash
551
 * @return string A hidden input field with a security hash
552
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::secure
553
 */
554
	public function secure($fields = array()) {
555
		if (!isset($this->request['_Token']) || empty($this->request['_Token'])) {
556
			return;
557
		}
558
		$locked = array();
559
		$unlockedFields = $this->_unlockedFields;
560
561
		foreach ($fields as $key => $value) {
562
			if (!is_int($key)) {
563
				$locked[$key] = $value;
564
				unset($fields[$key]);
565
			}
566
		}
567
568
		sort($unlockedFields, SORT_STRING);
569
		sort($fields, SORT_STRING);
570
		ksort($locked, SORT_STRING);
571
		$fields += $locked;
572
573
		$locked = implode(array_keys($locked), '|');
574
		$unlocked = implode($unlockedFields, '|');
575
		$hashParts = array(
576
			$this->_lastAction,
577
			serialize($fields),
578
			$unlocked,
579
			Configure::read('Security.salt')
580
		);
581
		$fields = Security::hash(implode('', $hashParts), 'sha1');
582
583
		$out = $this->hidden('_Token.fields', array(
584
			'value' => urlencode($fields . ':' . $locked),
585
			'id' => 'TokenFields' . mt_rand()
586
		));
587
		$out .= $this->hidden('_Token.unlocked', array(
588
			'value' => urlencode($unlocked),
589
			'id' => 'TokenUnlocked' . mt_rand()
590
		));
591
		return $this->Html->useTag('hiddenblock', $out);
592
	}
593
594
/**
595
 * Add to or get the list of fields that are currently unlocked.
596
 * Unlocked fields are not included in the field hash used by SecurityComponent
597
 * unlocking a field once its been added to the list of secured fields will remove
598
 * it from the list of fields.
599
 *
600
 * @param string $name The dot separated name for the field.
601
 * @return mixed Either null, or the list of fields.
602
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::unlockField
603
 */
604
	public function unlockField($name = null) {
605
		if ($name === null) {
606
			return $this->_unlockedFields;
607
		}
608
		if (!in_array($name, $this->_unlockedFields)) {
609
			$this->_unlockedFields[] = $name;
610
		}
611
		$index = array_search($name, $this->fields);
612
		if ($index !== false) {
613
			unset($this->fields[$index]);
614
		}
615
		unset($this->fields[$name]);
616
	}
617
618
/**
619
 * Determine which fields of a form should be used for hash.
620
 * Populates $this->fields
621
 *
622
 * @param boolean $lock Whether this field should be part of the validation
623
 *     or excluded as part of the unlockedFields.
624
 * @param string $field Reference to field to be secured. Should be dot separated to indicate nesting.
625
 * @param mixed $value Field value, if value should not be tampered with.
626
 * @return mixed|null Not used yet
627
 */
628
	protected function _secure($lock, $field = null, $value = null) {
629
		if (!$field) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $field of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

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

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

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

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
630
			$field = $this->entity();
631
		} elseif (is_string($field)) {
632
			$field = Hash::filter(explode('.', $field));
633
		}
634
635
		foreach ($this->_unlockedFields as $unlockField) {
636
			$unlockParts = explode('.', $unlockField);
637
			if (array_values(array_intersect($field, $unlockParts)) === $unlockParts) {
638
				return;
639
			}
640
		}
641
642
		$field = implode('.', $field);
643
		$field = preg_replace('/(\.\d+)+$/', '', $field);
644
645
		if ($lock) {
646
			if (!in_array($field, $this->fields)) {
647
				if ($value !== null) {
648
					return $this->fields[$field] = $value;
649
				}
650
				$this->fields[] = $field;
651
			}
652
		} else {
653
			$this->unlockField($field);
654
		}
655
	}
656
657
/**
658
 * Returns true if there is an error for the given field, otherwise false
659
 *
660
 * @param string $field This should be "Modelname.fieldname"
661
 * @return boolean If there are errors this method returns true, else false.
662
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::isFieldError
663
 */
664
	public function isFieldError($field) {
665
		$this->setEntity($field);
666
		return (bool)$this->tagIsInvalid();
667
	}
668
669
/**
670
 * Returns a formatted error message for given FORM field, NULL if no errors.
671
 *
672
 * ### Options:
673
 *
674
 * - `escape` boolean - Whether or not to html escape the contents of the error.
675
 * - `wrap` mixed - Whether or not the error message should be wrapped in a div. If a
676
 *   string, will be used as the HTML tag to use.
677
 * - `class` string - The class name for the error message
678
 *
679
 * @param string $field A field name, like "Modelname.fieldname"
680
 * @param string|array $text Error message as string or array of messages.
681
 *   If array contains `attributes` key it will be used as options for error container
682
 * @param array $options Rendering options for <div /> wrapper tag
683
 * @return string If there are errors this method returns an error message, otherwise null.
684
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::error
685
 */
686
	public function error($field, $text = null, $options = array()) {
687
		$defaults = array('wrap' => true, 'class' => 'error-message', 'escape' => true);
688
		$options = array_merge($defaults, $options);
689
		$this->setEntity($field);
690
691
		$error = $this->tagIsInvalid();
692
		if ($error === false) {
693
			return null;
694
		}
695
		if (is_array($text)) {
696 View Code Duplication
			if (isset($text['attributes']) && is_array($text['attributes'])) {
697
				$options = array_merge($options, $text['attributes']);
698
				unset($text['attributes']);
699
			}
700
			$tmp = array();
701
			foreach ($error as &$e) {
702
				if (isset($text[$e])) {
703
					$tmp[] = $text[$e];
704
				} else {
705
					$tmp[] = $e;
706
				}
707
			}
708
			$text = $tmp;
709
		}
710
711
		if ($text !== null) {
712
			$error = $text;
713
		}
714
		if (is_array($error)) {
715
			foreach ($error as &$e) {
716
				if (is_numeric($e)) {
717
					$e = __d('cake', 'Error in field %s', Inflector::humanize($this->field()));
718
				}
719
			}
720
		}
721
		if ($options['escape']) {
722
			$error = h($error);
723
			unset($options['escape']);
724
		}
725
		if (is_array($error)) {
726
			if (count($error) > 1) {
727
				$listParams = array();
728
				if (isset($options['listOptions'])) {
729
					if (is_string($options['listOptions'])) {
730
						$listParams[] = $options['listOptions'];
731
					} else {
732 View Code Duplication
						if (isset($options['listOptions']['itemOptions'])) {
733
							$listParams[] = $options['listOptions']['itemOptions'];
734
							unset($options['listOptions']['itemOptions']);
735
						} else {
736
							$listParams[] = array();
737
						}
738 View Code Duplication
						if (isset($options['listOptions']['tag'])) {
739
							$listParams[] = $options['listOptions']['tag'];
740
							unset($options['listOptions']['tag']);
741
						}
742
						array_unshift($listParams, $options['listOptions']);
743
					}
744
					unset($options['listOptions']);
745
				}
746
				array_unshift($listParams, $error);
747
				$error = call_user_func_array(array($this->Html, 'nestedList'), $listParams);
748
			} else {
749
				$error = array_pop($error);
750
			}
751
		}
752
		if ($options['wrap']) {
753
			$tag = is_string($options['wrap']) ? $options['wrap'] : 'div';
754
			unset($options['wrap']);
755
			return $this->Html->tag($tag, $error, $options);
756
		}
757
		return $error;
758
	}
759
760
/**
761
 * Returns a formatted LABEL element for HTML FORMs. Will automatically generate
762
 * a `for` attribute if one is not provided.
763
 *
764
 * ### Options
765
 *
766
 * - `for` - Set the for attribute, if its not defined the for attribute
767
 *   will be generated from the $fieldName parameter using
768
 *   FormHelper::domId().
769
 *
770
 * Examples:
771
 *
772
 * The text and for attribute are generated off of the fieldname
773
 *
774
 * {{{
775
 * echo $this->Form->label('Post.published');
776
 * <label for="PostPublished">Published</label>
777
 * }}}
778
 *
779
 * Custom text:
780
 *
781
 * {{{
782
 * echo $this->Form->label('Post.published', 'Publish');
783
 * <label for="PostPublished">Publish</label>
784
 * }}}
785
 *
786
 * Custom class name:
787
 *
788
 * {{{
789
 * echo $this->Form->label('Post.published', 'Publish', 'required');
790
 * <label for="PostPublished" class="required">Publish</label>
791
 * }}}
792
 *
793
 * Custom attributes:
794
 *
795
 * {{{
796
 * echo $this->Form->label('Post.published', 'Publish', array(
797
 *		'for' => 'post-publish'
798
 * ));
799
 * <label for="post-publish">Publish</label>
800
 * }}}
801
 *
802
 * @param string $fieldName This should be "Modelname.fieldname"
803
 * @param string $text Text that will appear in the label field. If
804
 *   $text is left undefined the text will be inflected from the
805
 *   fieldName.
806
 * @param array|string $options An array of HTML attributes, or a string, to be used as a class name.
807
 * @return string The formatted LABEL element
808
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::label
809
 */
810
	public function label($fieldName = null, $text = null, $options = array()) {
811
		if ($fieldName === null) {
812
			$fieldName = implode('.', $this->entity());
813
		}
814
815
		if ($text === null) {
816
			if (strpos($fieldName, '.') !== false) {
817
				$fieldElements = explode('.', $fieldName);
818
				$text = array_pop($fieldElements);
819
			} else {
820
				$text = $fieldName;
821
			}
822 View Code Duplication
			if (substr($text, -3) === '_id') {
823
				$text = substr($text, 0, -3);
824
			}
825
			$text = __(Inflector::humanize(Inflector::underscore($text)));
826
		}
827
828
		if (is_string($options)) {
829
			$options = array('class' => $options);
830
		}
831
832
		if (isset($options['for'])) {
833
			$labelFor = $options['for'];
834
			unset($options['for']);
835
		} else {
836
			$labelFor = $this->domId($fieldName);
837
		}
838
839
		return $this->Html->useTag('label', $labelFor, $options, $text);
840
	}
841
842
/**
843
 * Generate a set of inputs for `$fields`. If $fields is null the fields of current model
844
 * will be used.
845
 *
846
 * You can customize individual inputs through `$fields`.
847
 * {{{
848
 *	$this->Form->inputs(array(
849
 *		'name' => array('label' => 'custom label')
850
 *	));
851
 * }}}
852
 *
853
 * In addition to controller fields output, `$fields` can be used to control legend
854
 * and fieldset rendering.
855
 * `$this->Form->inputs('My legend');` Would generate an input set with a custom legend.
856
 * Passing `fieldset` and `legend` key in `$fields` array has been deprecated since 2.3,
857
 * for more fine grained control use the `fieldset` and `legend` keys in `$options` param.
858
 *
859
 * @param array $fields An array of fields to generate inputs for, or null.
860
 * @param array $blacklist A simple array of fields to not create inputs for.
861
 * @param array $options Options array. Valid keys are:
862
 * - `fieldset` Set to false to disable the fieldset. If a string is supplied it will be used as
863
 *    the class name for the fieldset element.
864
 * - `legend` Set to false to disable the legend for the generated input set. Or supply a string
865
 *    to customize the legend text.
866
 * @return string Completed form inputs.
867
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::inputs
868
 */
869
	public function inputs($fields = null, $blacklist = null, $options = array()) {
870
		$fieldset = $legend = true;
871
		$modelFields = array();
872
		$model = $this->model();
873
		if ($model) {
874
			$modelFields = array_keys((array)$this->_introspectModel($model, 'fields'));
875
		}
876
		if (is_array($fields)) {
877
			if (array_key_exists('legend', $fields) && !in_array('legend', $modelFields)) {
878
				$legend = $fields['legend'];
879
				unset($fields['legend']);
880
			}
881
882
			if (isset($fields['fieldset']) && !in_array('fieldset', $modelFields)) {
883
				$fieldset = $fields['fieldset'];
884
				unset($fields['fieldset']);
885
			}
886
		} elseif ($fields !== null) {
887
			$fieldset = $legend = $fields;
888
			if (!is_bool($fieldset)) {
889
				$fieldset = true;
890
			}
891
			$fields = array();
892
		}
893
894
		if (isset($options['legend'])) {
895
			$legend = $options['legend'];
896
		}
897
		if (isset($options['fieldset'])) {
898
			$fieldset = $options['fieldset'];
899
		}
900
901
		if (empty($fields)) {
902
			$fields = $modelFields;
903
		}
904
905
		if ($legend === true) {
906
			$actionName = __d('cake', 'New %s');
907
			$isEdit = (
908
				strpos($this->request->params['action'], 'update') !== false ||
909
				strpos($this->request->params['action'], 'edit') !== false
910
			);
911
			if ($isEdit) {
912
				$actionName = __d('cake', 'Edit %s');
913
			}
914
			$modelName = Inflector::humanize(Inflector::underscore($model));
915
			$legend = sprintf($actionName, __($modelName));
916
		}
917
918
		$out = null;
919
		foreach ($fields as $name => $options) {
920
			if (is_numeric($name) && !is_array($options)) {
921
				$name = $options;
922
				$options = array();
923
			}
924
			$entity = explode('.', $name);
925
			$blacklisted = (
926
				is_array($blacklist) &&
927
				(in_array($name, $blacklist) || in_array(end($entity), $blacklist))
928
			);
929
			if ($blacklisted) {
930
				continue;
931
			}
932
			$out .= $this->input($name, $options);
933
		}
934
935
		if (is_string($fieldset)) {
936
			$fieldsetClass = sprintf(' class="%s"', $fieldset);
937
		} else {
938
			$fieldsetClass = '';
939
		}
940
941
		if ($fieldset) {
942
			if ($legend) {
943
				$out = $this->Html->useTag('legend', $legend) . $out;
944
			}
945
			$out = $this->Html->useTag('fieldset', $fieldsetClass, $out);
946
		}
947
		return $out;
948
	}
949
950
/**
951
 * Generates a form input element complete with label and wrapper div
952
 *
953
 * ### Options
954
 *
955
 * See each field type method for more information. Any options that are part of
956
 * $attributes or $options for the different **type** methods can be included in `$options` for input().i
957
 * Additionally, any unknown keys that are not in the list below, or part of the selected type's options
958
 * will be treated as a regular html attribute for the generated input.
959
 *
960
 * - `type` - Force the type of widget you want. e.g. `type => 'select'`
961
 * - `label` - Either a string label, or an array of options for the label. See FormHelper::label().
962
 * - `div` - Either `false` to disable the div, or an array of options for the div.
963
 *	See HtmlHelper::div() for more options.
964
 * - `options` - For widgets that take options e.g. radio, select.
965
 * - `error` - Control the error message that is produced. Set to `false` to disable any kind of error reporting (field
966
 *    error and error messages).
967
 * - `errorMessage` - Boolean to control rendering error messages (field error will still occur).
968
 * - `empty` - String or boolean to enable empty select box options.
969
 * - `before` - Content to place before the label + input.
970
 * - `after` - Content to place after the label + input.
971
 * - `between` - Content to place between the label + input.
972
 * - `format` - Format template for element order. Any element that is not in the array, will not be in the output.
973
 *	- Default input format order: array('before', 'label', 'between', 'input', 'after', 'error')
974
 *	- Default checkbox format order: array('before', 'input', 'between', 'label', 'after', 'error')
975
 *	- Hidden input will not be formatted
976
 *	- Radio buttons cannot have the order of input and label elements controlled with these settings.
977
 *
978
 * @param string $fieldName This should be "Modelname.fieldname"
979
 * @param array $options Each type of input takes different options.
980
 * @return string Completed form widget.
981
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#creating-form-elements
982
 */
983
	public function input($fieldName, $options = array()) {
984
		$this->setEntity($fieldName);
985
		$options = $this->_parseOptions($options);
986
987
		$divOptions = $this->_divOptions($options);
988
		unset($options['div']);
989
990 View Code Duplication
		if ($options['type'] === 'radio' && isset($options['options'])) {
991
			$radioOptions = (array)$options['options'];
992
			unset($options['options']);
993
		}
994
995
		$label = $this->_getLabel($fieldName, $options);
996
		if ($options['type'] !== 'radio') {
997
			unset($options['label']);
998
		}
999
1000
		$error = $this->_extractOption('error', $options, null);
1001
		unset($options['error']);
1002
1003
		$errorMessage = $this->_extractOption('errorMessage', $options, true);
1004
		unset($options['errorMessage']);
1005
1006
		$selected = $this->_extractOption('selected', $options, null);
1007
		unset($options['selected']);
1008
1009
		if ($options['type'] === 'datetime' || $options['type'] === 'date' || $options['type'] === 'time') {
1010
			$dateFormat = $this->_extractOption('dateFormat', $options, 'MDY');
1011
			$timeFormat = $this->_extractOption('timeFormat', $options, 12);
1012
			unset($options['dateFormat'], $options['timeFormat']);
1013
		}
1014
1015
		$type = $options['type'];
1016
		$out = array('before' => $options['before'], 'label' => $label, 'between' => $options['between'], 'after' => $options['after']);
1017
		$format = $this->_getFormat($options);
1018
1019
		unset($options['type'], $options['before'], $options['between'], $options['after'], $options['format']);
1020
1021
		$out['error'] = null;
1022
		if ($type !== 'hidden' && $error !== false) {
1023
			$errMsg = $this->error($fieldName, $error);
1024
			if ($errMsg) {
1025
				$divOptions = $this->addClass($divOptions, 'error');
1026
				if ($errorMessage) {
1027
					$out['error'] = $errMsg;
1028
				}
1029
			}
1030
		}
1031
1032 View Code Duplication
		if ($type === 'radio' && isset($out['between'])) {
1033
			$options['between'] = $out['between'];
1034
			$out['between'] = null;
1035
		}
1036
		$out['input'] = $this->_getInput(compact('type', 'fieldName', 'options', 'radioOptions', 'selected', 'dateFormat', 'timeFormat'));
1037
1038
		$output = '';
1039
		foreach ($format as $element) {
1040
			$output .= $out[$element];
1041
		}
1042
1043
		if (!empty($divOptions['tag'])) {
1044
			$tag = $divOptions['tag'];
1045
			unset($divOptions['tag']);
1046
			$output = $this->Html->tag($tag, $output, $divOptions);
1047
		}
1048
		return $output;
1049
	}
1050
1051
/**
1052
 * Generates an input element
1053
 *
1054
 * @param array $args The options for the input element
1055
 * @return string The generated input element
1056
 */
1057
	protected function _getInput($args) {
1058
		extract($args);
1059
		switch ($type) {
1060
			case 'hidden':
1061
				return $this->hidden($fieldName, $options);
1062
			case 'checkbox':
1063
				return $this->checkbox($fieldName, $options);
1064
			case 'radio':
1065
				return $this->radio($fieldName, $radioOptions, $options);
1066
			case 'file':
1067
				return $this->file($fieldName, $options);
1068
			case 'select':
1069
				$options += array('options' => array(), 'value' => $selected);
1070
				$list = $options['options'];
1071
				unset($options['options']);
1072
				return $this->select($fieldName, $list, $options);
1073
			case 'time':
1074
				$options['value'] = $selected;
1075
				return $this->dateTime($fieldName, null, $timeFormat, $options);
1076
			case 'date':
1077
				$options['value'] = $selected;
1078
				return $this->dateTime($fieldName, $dateFormat, null, $options);
1079
			case 'datetime':
1080
				$options['value'] = $selected;
1081
				return $this->dateTime($fieldName, $dateFormat, $timeFormat, $options);
1082
			case 'textarea':
1083
				return $this->textarea($fieldName, $options + array('cols' => '30', 'rows' => '6'));
1084
			case 'url':
1085
				return $this->text($fieldName, array('type' => 'url') + $options);
0 ignored issues
show
Documentation Bug introduced by
The method text does not exist on object<FormHelper>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1086
			default:
1087
				return $this->{$type}($fieldName, $options);
1088
		}
1089
	}
1090
1091
/**
1092
 * Generates input options array
1093
 *
1094
 * @param array $options
1095
 * @return array Options
1096
 */
1097
	protected function _parseOptions($options) {
1098
		$options = array_merge(
1099
			array('before' => null, 'between' => null, 'after' => null, 'format' => null),
1100
			$this->_inputDefaults,
1101
			$options
1102
		);
1103
1104
		if (!isset($options['type'])) {
1105
			$options = $this->_magicOptions($options);
1106
		}
1107
1108
		if (in_array($options['type'], array('radio', 'select'))) {
1109
			$options = $this->_optionsOptions($options);
1110
		}
1111
1112
		if (isset($options['rows']) || isset($options['cols'])) {
1113
			$options['type'] = 'textarea';
1114
		}
1115
1116
		if ($options['type'] === 'datetime' || $options['type'] === 'date' || $options['type'] === 'time' || $options['type'] === 'select') {
1117
			$options += array('empty' => false);
1118
		}
1119
		return $options;
1120
	}
1121
1122
/**
1123
 * Generates list of options for multiple select
1124
 *
1125
 * @param array $options
1126
 * @return array
1127
 */
1128
	protected function _optionsOptions($options) {
1129
		if (isset($options['options'])) {
1130
			return $options;
1131
		}
1132
		$varName = Inflector::variable(
1133
			Inflector::pluralize(preg_replace('/_id$/', '', $this->field()))
1134
		);
1135
		$varOptions = $this->_View->get($varName);
1136
		if (!is_array($varOptions)) {
1137
			return $options;
1138
		}
1139
		if ($options['type'] !== 'radio') {
1140
			$options['type'] = 'select';
1141
		}
1142
		$options['options'] = $varOptions;
1143
		return $options;
1144
	}
1145
1146
/**
1147
 * Magically set option type and corresponding options
1148
 *
1149
 * @param array $options
1150
 * @return array
1151
 */
1152
	protected function _magicOptions($options) {
1153
		$modelKey = $this->model();
1154
		$fieldKey = $this->field();
1155
		$options['type'] = 'text';
1156
		if (isset($options['options'])) {
1157
			$options['type'] = 'select';
1158
		} elseif (in_array($fieldKey, array('psword', 'passwd', 'password'))) {
1159
			$options['type'] = 'password';
1160
		} elseif (in_array($fieldKey, array('tel', 'telephone', 'phone'))) {
1161
			$options['type'] = 'tel';
1162
		} elseif ($fieldKey === 'email') {
1163
			$options['type'] = 'email';
1164
		} elseif (isset($options['checked'])) {
1165
			$options['type'] = 'checkbox';
1166
		} elseif ($fieldDef = $this->_introspectModel($modelKey, 'fields', $fieldKey)) {
1167
			$type = $fieldDef['type'];
1168
			$primaryKey = $this->fieldset[$modelKey]['key'];
1169
			$map = array(
1170
				'string' => 'text', 'datetime' => 'datetime',
1171
				'boolean' => 'checkbox', 'timestamp' => 'datetime',
1172
				'text' => 'textarea', 'time' => 'time',
1173
				'date' => 'date', 'float' => 'number',
1174
				'integer' => 'number'
1175
			);
1176
1177 View Code Duplication
			if (isset($this->map[$type])) {
1178
				$options['type'] = $this->map[$type];
1179
			} elseif (isset($map[$type])) {
1180
				$options['type'] = $map[$type];
1181
			}
1182
			if ($fieldKey === $primaryKey) {
1183
				$options['type'] = 'hidden';
1184
			}
1185
			if (
1186
				$options['type'] === 'number' &&
1187
				$type === 'float' &&
1188
				!isset($options['step'])
1189
			) {
1190
				$options['step'] = 'any';
1191
			}
1192
		}
1193
1194
		if (preg_match('/_id$/', $fieldKey) && $options['type'] !== 'hidden') {
1195
			$options['type'] = 'select';
1196
		}
1197
1198
		if ($modelKey === $fieldKey) {
1199
			$options['type'] = 'select';
1200
			if (!isset($options['multiple'])) {
1201
				$options['multiple'] = 'multiple';
1202
			}
1203
		}
1204
		if (in_array($options['type'], array('text', 'number'))) {
1205
			$options = $this->_optionsOptions($options);
1206
		}
1207
		if ($options['type'] === 'select' && array_key_exists('step', $options)) {
1208
			unset($options['step']);
1209
		}
1210
		$options = $this->_maxLength($options);
1211
		return $options;
1212
	}
1213
1214
/**
1215
 * Generate format options
1216
 *
1217
 * @param array $options
1218
 * @return array
1219
 */
1220
	protected function _getFormat($options) {
1221
		if ($options['type'] === 'hidden') {
1222
			return array('input');
1223
		}
1224
		if (is_array($options['format']) && in_array('input', $options['format'])) {
1225
			return $options['format'];
1226
		}
1227
		if ($options['type'] === 'checkbox') {
1228
			return array('before', 'input', 'between', 'label', 'after', 'error');
1229
		}
1230
		return array('before', 'label', 'between', 'input', 'after', 'error');
1231
	}
1232
1233
/**
1234
 * Generate label for input
1235
 *
1236
 * @param string $fieldName
1237
 * @param array $options
1238
 * @return boolean|string false or Generated label element
1239
 */
1240
	protected function _getLabel($fieldName, $options) {
1241
		if ($options['type'] === 'radio') {
1242
			return false;
1243
		}
1244
1245
		$label = null;
1246
		if (isset($options['label'])) {
1247
			$label = $options['label'];
1248
		}
1249
1250
		if ($label === false) {
1251
			return false;
1252
		}
1253
		return $this->_inputLabel($fieldName, $label, $options);
1254
	}
1255
1256
/**
1257
 * Calculates maxlength option
1258
 *
1259
 * @param array $options
1260
 * @return array
1261
 */
1262
	protected function _maxLength($options) {
1263
		$fieldDef = $this->_introspectModel($this->model(), 'fields', $this->field());
1264
		$autoLength = (
1265
			!array_key_exists('maxlength', $options) &&
1266
			isset($fieldDef['length']) &&
1267
			is_scalar($fieldDef['length']) &&
1268
			$options['type'] !== 'select'
1269
		);
1270
		if ($autoLength &&
1271
			in_array($options['type'], array('text', 'email', 'tel', 'url', 'search'))
1272
		) {
1273
			$options['maxlength'] = $fieldDef['length'];
1274
		}
1275
		return $options;
1276
	}
1277
1278
/**
1279
 * Generate div options for input
1280
 *
1281
 * @param array $options
1282
 * @return array
1283
 */
1284
	protected function _divOptions($options) {
1285
		if ($options['type'] === 'hidden') {
1286
			return array();
1287
		}
1288
		$div = $this->_extractOption('div', $options, true);
1289
		if (!$div) {
1290
			return array();
1291
		}
1292
1293
		$divOptions = array('class' => 'input');
1294
		$divOptions = $this->addClass($divOptions, $options['type']);
1295
		if (is_string($div)) {
1296
			$divOptions['class'] = $div;
1297
		} elseif (is_array($div)) {
1298
			$divOptions = array_merge($divOptions, $div);
1299
		}
1300
		if (
1301
			$this->_extractOption('required', $options) !== false &&
1302
			$this->_introspectModel($this->model(), 'validates', $this->field())
1303
		) {
1304
			$divOptions = $this->addClass($divOptions, 'required');
1305
		}
1306
		if (!isset($divOptions['tag'])) {
1307
			$divOptions['tag'] = 'div';
1308
		}
1309
		return $divOptions;
1310
	}
1311
1312
/**
1313
 * Extracts a single option from an options array.
1314
 *
1315
 * @param string $name The name of the option to pull out.
1316
 * @param array $options The array of options you want to extract.
1317
 * @param mixed $default The default option value
1318
 * @return mixed the contents of the option or default
1319
 */
1320
	protected function _extractOption($name, $options, $default = null) {
1321
		if (array_key_exists($name, $options)) {
1322
			return $options[$name];
1323
		}
1324
		return $default;
1325
	}
1326
1327
/**
1328
 * Generate a label for an input() call.
1329
 *
1330
 * $options can contain a hash of id overrides. These overrides will be
1331
 * used instead of the generated values if present.
1332
 *
1333
 * @param string $fieldName
1334
 * @param string $label
1335
 * @param array $options Options for the label element. 'NONE' option is deprecated and will be removed in 3.0
1336
 * @return string Generated label element
1337
 */
1338
	protected function _inputLabel($fieldName, $label, $options) {
1339
		$labelAttributes = $this->domId(array(), 'for');
1340
		$idKey = null;
1341
		if ($options['type'] === 'date' || $options['type'] === 'datetime') {
1342
			$firstInput = 'M';
1343
			if (
1344
				array_key_exists('dateFormat', $options) &&
1345
				($options['dateFormat'] === null || $options['dateFormat'] === 'NONE')
1346
			) {
1347
				$firstInput = 'H';
1348
			} elseif (!empty($options['dateFormat'])) {
1349
				$firstInput = substr($options['dateFormat'], 0, 1);
1350
			}
1351
			switch ($firstInput) {
1352
				case 'D':
1353
					$idKey = 'day';
1354
					$labelAttributes['for'] .= 'Day';
1355
					break;
1356
				case 'Y':
1357
					$idKey = 'year';
1358
					$labelAttributes['for'] .= 'Year';
1359
					break;
1360
				case 'M':
1361
					$idKey = 'month';
1362
					$labelAttributes['for'] .= 'Month';
1363
					break;
1364
				case 'H':
1365
					$idKey = 'hour';
1366
					$labelAttributes['for'] .= 'Hour';
1367
			}
1368
		}
1369
		if ($options['type'] === 'time') {
1370
			$labelAttributes['for'] .= 'Hour';
1371
			$idKey = 'hour';
1372
		}
1373
		if (isset($idKey) && isset($options['id']) && isset($options['id'][$idKey])) {
1374
			$labelAttributes['for'] = $options['id'][$idKey];
1375
		}
1376
1377
		if (is_array($label)) {
1378
			$labelText = null;
1379
			if (isset($label['text'])) {
1380
				$labelText = $label['text'];
1381
				unset($label['text']);
1382
			}
1383
			$labelAttributes = array_merge($labelAttributes, $label);
1384
		} else {
1385
			$labelText = $label;
1386
		}
1387
1388
		if (isset($options['id']) && is_string($options['id'])) {
1389
			$labelAttributes = array_merge($labelAttributes, array('for' => $options['id']));
1390
		}
1391
		return $this->label($fieldName, $labelText, $labelAttributes);
1392
	}
1393
1394
/**
1395
 * Creates a checkbox input widget.
1396
 *
1397
 * ### Options:
1398
 *
1399
 * - `value` - the value of the checkbox
1400
 * - `checked` - boolean indicate that this checkbox is checked.
1401
 * - `hiddenField` - boolean to indicate if you want the results of checkbox() to include
1402
 *    a hidden input with a value of ''.
1403
 * - `disabled` - create a disabled input.
1404
 * - `default` - Set the default value for the checkbox. This allows you to start checkboxes
1405
 *    as checked, without having to check the POST data. A matching POST data value, will overwrite
1406
 *    the default value.
1407
 *
1408
 * @param string $fieldName Name of a field, like this "Modelname.fieldname"
1409
 * @param array $options Array of HTML attributes.
1410
 * @return string An HTML text input element.
1411
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#options-for-select-checkbox-and-radio-inputs
1412
 */
1413
	public function checkbox($fieldName, $options = array()) {
1414
		$valueOptions = array();
1415
		if (isset($options['default'])) {
1416
			$valueOptions['default'] = $options['default'];
1417
			unset($options['default']);
1418
		}
1419
1420
		$options += array('value' => 1, 'required' => false);
1421
		$options = $this->_initInputField($fieldName, $options) + array('hiddenField' => true);
1422
		$value = current($this->value($valueOptions));
1423
		$output = '';
1424
1425
		if (
1426
			(!isset($options['checked']) && !empty($value) && $value == $options['value']) ||
1427
			!empty($options['checked'])
1428
		) {
1429
			$options['checked'] = 'checked';
1430
		}
1431
		if ($options['hiddenField']) {
1432
			$hiddenOptions = array(
1433
				'id' => $options['id'] . '_',
1434
				'name' => $options['name'],
1435
				'value' => ($options['hiddenField'] !== true ? $options['hiddenField'] : '0'),
1436
				'form' => isset($options['form']) ? $options['form'] : null,
1437
				'secure' => false,
1438
			);
1439
			if (isset($options['disabled']) && $options['disabled']) {
1440
				$hiddenOptions['disabled'] = 'disabled';
1441
			}
1442
			$output = $this->hidden($fieldName, $hiddenOptions);
1443
		}
1444
		unset($options['hiddenField']);
1445
1446
		return $output . $this->Html->useTag('checkbox', $options['name'], array_diff_key($options, array('name' => null)));
1447
	}
1448
1449
/**
1450
 * Creates a set of radio widgets. Will create a legend and fieldset
1451
 * by default. Use $options to control this
1452
 *
1453
 * ### Attributes:
1454
 *
1455
 * - `separator` - define the string in between the radio buttons
1456
 * - `between` - the string between legend and input set or array of strings to insert
1457
 *    strings between each input block
1458
 * - `legend` - control whether or not the widget set has a fieldset & legend
1459
 * - `value` - indicate a value that is should be checked
1460
 * - `label` - boolean to indicate whether or not labels for widgets show be displayed
1461
 * - `hiddenField` - boolean to indicate if you want the results of radio() to include
1462
 *    a hidden input with a value of ''. This is useful for creating radio sets that non-continuous
1463
 * - `disabled` - Set to `true` or `disabled` to disable all the radio buttons.
1464
 * - `empty` - Set to `true` to create a input with the value '' as the first option. When `true`
1465
 *   the radio label will be 'empty'. Set this option to a string to control the label value.
1466
 *
1467
 * @param string $fieldName Name of a field, like this "Modelname.fieldname"
1468
 * @param array $options Radio button options array.
1469
 * @param array $attributes Array of HTML attributes, and special attributes above.
1470
 * @return string Completed radio widget set.
1471
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#options-for-select-checkbox-and-radio-inputs
1472
 */
1473
	public function radio($fieldName, $options = array(), $attributes = array()) {
1474
		$attributes = $this->_initInputField($fieldName, $attributes);
1475
1476
		$showEmpty = $this->_extractOption('empty', $attributes);
1477
		if ($showEmpty) {
1478
			$showEmpty = ($showEmpty === true) ? __d('cake', 'empty') : $showEmpty;
1479
			$options = array('' => $showEmpty) + $options;
1480
		}
1481
		unset($attributes['empty']);
1482
1483
		$legend = false;
1484
		if (isset($attributes['legend'])) {
1485
			$legend = $attributes['legend'];
1486
			unset($attributes['legend']);
1487
		} elseif (count($options) > 1) {
1488
			$legend = __(Inflector::humanize($this->field()));
1489
		}
1490
1491
		$label = true;
1492
		if (isset($attributes['label'])) {
1493
			$label = $attributes['label'];
1494
			unset($attributes['label']);
1495
		}
1496
1497
		$separator = null;
1498
		if (isset($attributes['separator'])) {
1499
			$separator = $attributes['separator'];
1500
			unset($attributes['separator']);
1501
		}
1502
1503
		$between = null;
1504
		if (isset($attributes['between'])) {
1505
			$between = $attributes['between'];
1506
			unset($attributes['between']);
1507
		}
1508
1509
		$value = null;
1510
		if (isset($attributes['value'])) {
1511
			$value = $attributes['value'];
1512
		} else {
1513
			$value = $this->value($fieldName);
1514
		}
1515
1516
		$disabled = array();
1517
		if (isset($attributes['disabled'])) {
1518
			$disabled = $attributes['disabled'];
1519
		}
1520
1521
		$out = array();
1522
1523
		$hiddenField = isset($attributes['hiddenField']) ? $attributes['hiddenField'] : true;
1524
		unset($attributes['hiddenField']);
1525
1526
		if (isset($value) && is_bool($value)) {
1527
			$value = $value ? 1 : 0;
1528
		}
1529
1530
		$this->_domIdSuffixes = array();
1531
		foreach ($options as $optValue => $optTitle) {
1532
			$optionsHere = array('value' => $optValue, 'disabled' => false);
1533
1534
			if (isset($value) && strval($optValue) === strval($value)) {
1535
				$optionsHere['checked'] = 'checked';
1536
			}
1537
			$isNumeric = is_numeric($optValue);
1538
			if ($disabled && (!is_array($disabled) || in_array((string)$optValue, $disabled, !$isNumeric))) {
1539
				$optionsHere['disabled'] = true;
1540
			}
1541
			$tagName = $attributes['id'] . $this->domIdSuffix($optValue);
1542
1543
			if ($label) {
1544
				$labelOpts = is_array($label) ? $label : array();
1545
				$labelOpts += array('for' => $tagName);
1546
				$optTitle = $this->label($tagName, $optTitle, $labelOpts);
1547
			}
1548
1549
			if (is_array($between)) {
1550
				$optTitle .= array_shift($between);
1551
			}
1552
			$allOptions = array_merge($attributes, $optionsHere);
1553
			$out[] = $this->Html->useTag('radio', $attributes['name'], $tagName,
1554
				array_diff_key($allOptions, array('name' => null, 'type' => null, 'id' => null)),
1555
				$optTitle
1556
			);
1557
		}
1558
		$hidden = null;
1559
1560
		if ($hiddenField) {
1561
			if (!isset($value) || $value === '') {
1562
				$hidden = $this->hidden($fieldName, array(
1563
					'form' => isset($attributes['form']) ? $attributes['form'] : null,
1564
					'id' => $attributes['id'] . '_',
1565
					'value' => '',
1566
					'name' => $attributes['name']
1567
				));
1568
			}
1569
		}
1570
		$out = $hidden . implode($separator, $out);
1571
1572
		if (is_array($between)) {
1573
			$between = '';
1574
		}
1575
		if ($legend) {
1576
			$out = $this->Html->useTag('fieldset', '', $this->Html->useTag('legend', $legend) . $between . $out);
1577
		}
1578
		return $out;
1579
	}
1580
1581
/**
1582
 * Missing method handler - implements various simple input types. Is used to create inputs
1583
 * of various types. e.g. `$this->Form->text();` will create `<input type="text" />` while
1584
 * `$this->Form->range();` will create `<input type="range" />`
1585
 *
1586
 * ### Usage
1587
 *
1588
 * `$this->Form->search('User.query', array('value' => 'test'));`
1589
 *
1590
 * Will make an input like:
1591
 *
1592
 * `<input type="search" id="UserQuery" name="data[User][query]" value="test" />`
1593
 *
1594
 * The first argument to an input type should always be the fieldname, in `Model.field` format.
1595
 * The second argument should always be an array of attributes for the input.
1596
 *
1597
 * @param string $method Method name / input type to make.
1598
 * @param array $params Parameters for the method call
1599
 * @return string Formatted input method.
1600
 * @throws CakeException When there are no params for the method call.
1601
 */
1602
	public function __call($method, $params) {
1603
		$options = array();
1604
		if (empty($params)) {
1605
			throw new CakeException(__d('cake_dev', 'Missing field name for FormHelper::%s', $method));
1606
		}
1607
		if (isset($params[1])) {
1608
			$options = $params[1];
1609
		}
1610
		if (!isset($options['type'])) {
1611
			$options['type'] = $method;
1612
		}
1613
		$options = $this->_initInputField($params[0], $options);
1614
		return $this->Html->useTag('input', $options['name'], array_diff_key($options, array('name' => null)));
1615
	}
1616
1617
/**
1618
 * Creates a textarea widget.
1619
 *
1620
 * ### Options:
1621
 *
1622
 * - `escape` - Whether or not the contents of the textarea should be escaped. Defaults to true.
1623
 *
1624
 * @param string $fieldName Name of a field, in the form "Modelname.fieldname"
1625
 * @param array $options Array of HTML attributes, and special options above.
1626
 * @return string A generated HTML text input element
1627
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::textarea
1628
 */
1629
	public function textarea($fieldName, $options = array()) {
1630
		$options = $this->_initInputField($fieldName, $options);
1631
		$value = null;
1632
1633
		if (array_key_exists('value', $options)) {
1634
			$value = $options['value'];
1635
			if (!array_key_exists('escape', $options) || $options['escape'] !== false) {
1636
				$value = h($value);
1637
			}
1638
			unset($options['value']);
1639
		}
1640
		return $this->Html->useTag('textarea', $options['name'], array_diff_key($options, array('type' => null, 'name' => null)), $value);
1641
	}
1642
1643
/**
1644
 * Creates a hidden input field.
1645
 *
1646
 * @param string $fieldName Name of a field, in the form of "Modelname.fieldname"
1647
 * @param array $options Array of HTML attributes.
1648
 * @return string A generated hidden input
1649
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::hidden
1650
 */
1651
	public function hidden($fieldName, $options = array()) {
1652
		$options += array('required' => false, 'secure' => true);
1653
1654
		$secure = $options['secure'];
1655
		unset($options['secure']);
1656
1657
		$options = $this->_initInputField($fieldName, array_merge(
1658
			$options, array('secure' => self::SECURE_SKIP)
1659
		));
1660
1661
		if ($secure === true) {
1662
			$this->_secure(true, null, '' . $options['value']);
1663
		}
1664
1665
		return $this->Html->useTag('hidden', $options['name'], array_diff_key($options, array('name' => null)));
1666
	}
1667
1668
/**
1669
 * Creates file input widget.
1670
 *
1671
 * @param string $fieldName Name of a field, in the form "Modelname.fieldname"
1672
 * @param array $options Array of HTML attributes.
1673
 * @return string A generated file input.
1674
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::file
1675
 */
1676
	public function file($fieldName, $options = array()) {
1677
		$options += array('secure' => true);
1678
		$secure = $options['secure'];
1679
		$options['secure'] = self::SECURE_SKIP;
1680
1681
		$options = $this->_initInputField($fieldName, $options);
1682
		$field = $this->entity();
1683
1684
		foreach (array('name', 'type', 'tmp_name', 'error', 'size') as $suffix) {
1685
			$this->_secure($secure, array_merge($field, array($suffix)));
1686
		}
1687
1688
		$exclude = array('name' => null, 'value' => null);
1689
		return $this->Html->useTag('file', $options['name'], array_diff_key($options, $exclude));
1690
	}
1691
1692
/**
1693
 * Creates a `<button>` tag. The type attribute defaults to `type="submit"`
1694
 * You can change it to a different value by using `$options['type']`.
1695
 *
1696
 * ### Options:
1697
 *
1698
 * - `escape` - HTML entity encode the $title of the button. Defaults to false.
1699
 *
1700
 * @param string $title The button's caption. Not automatically HTML encoded
1701
 * @param array $options Array of options and HTML attributes.
1702
 * @return string A HTML button tag.
1703
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::button
1704
 */
1705
	public function button($title, $options = array()) {
1706
		$options += array('type' => 'submit', 'escape' => false, 'secure' => false);
1707
		if ($options['escape']) {
1708
			$title = h($title);
1709
		}
1710 View Code Duplication
		if (isset($options['name'])) {
1711
			$name = str_replace(array('[', ']'), array('.', ''), $options['name']);
1712
			$this->_secure($options['secure'], $name);
1713
		}
1714
		return $this->Html->useTag('button', $options, $title);
1715
	}
1716
1717
/**
1718
 * Create a `<button>` tag with a surrounding `<form>` that submits via POST.
1719
 *
1720
 * This method creates a `<form>` element. So do not use this method in an already opened form.
1721
 * Instead use FormHelper::submit() or FormHelper::button() to create buttons inside opened forms.
1722
 *
1723
 * ### Options:
1724
 *
1725
 * - `data` - Array with key/value to pass in input hidden
1726
 * - Other options is the same of button method.
1727
 *
1728
 * @param string $title The button's caption. Not automatically HTML encoded
1729
 * @param string|array $url URL as string or array
1730
 * @param array $options Array of options and HTML attributes.
1731
 * @return string A HTML button tag.
1732
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::postButton
1733
 */
1734
	public function postButton($title, $url, $options = array()) {
1735
		$out = $this->create(false, array('id' => false, 'url' => $url));
1736
		if (isset($options['data']) && is_array($options['data'])) {
1737
			foreach (Hash::flatten($options['data']) as $key => $value) {
1738
				$out .= $this->hidden($key, array('value' => $value, 'id' => false));
1739
			}
1740
			unset($options['data']);
1741
		}
1742
		$out .= $this->button($title, $options);
1743
		$out .= $this->end();
1744
		return $out;
1745
	}
1746
1747
/**
1748
 * Creates an HTML link, but access the URL using the method you specify (defaults to POST).
1749
 * Requires javascript to be enabled in browser.
1750
 *
1751
 * This method creates a `<form>` element. So do not use this method inside an existing form.
1752
 * Instead you should add a submit button using FormHelper::submit()
1753
 *
1754
 * ### Options:
1755
 *
1756
 * - `data` - Array with key/value to pass in input hidden
1757
 * - `method` - Request method to use. Set to 'delete' to simulate HTTP/1.1 DELETE request. Defaults to 'post'.
1758
 * - `confirm` - Can be used instead of $confirmMessage.
1759
 * - Other options is the same of HtmlHelper::link() method.
1760
 * - The option `onclick` will be replaced.
1761
 *
1762
 * @param string $title The content to be wrapped by <a> tags.
1763
 * @param string|array $url Cake-relative URL or array of URL parameters, or external URL (starts with http://)
1764
 * @param array $options Array of HTML attributes.
1765
 * @param boolean|string $confirmMessage JavaScript confirmation message.
1766
 * @return string An `<a />` element.
1767
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::postLink
1768
 */
1769
	public function postLink($title, $url = null, $options = array(), $confirmMessage = false) {
1770
		$requestMethod = 'POST';
1771
		if (!empty($options['method'])) {
1772
			$requestMethod = strtoupper($options['method']);
1773
			unset($options['method']);
1774
		}
1775
		if (!empty($options['confirm'])) {
1776
			$confirmMessage = $options['confirm'];
1777
			unset($options['confirm']);
1778
		}
1779
1780
		$formName = str_replace('.', '', uniqid('post_', true));
1781
		$formUrl = $this->url($url);
1782
		$formOptions = array(
1783
			'name' => $formName,
1784
			'id' => $formName,
1785
			'style' => 'display:none;',
1786
			'method' => 'post',
1787
		);
1788
		if (isset($options['target'])) {
1789
			$formOptions['target'] = $options['target'];
1790
			unset($options['target']);
1791
		}
1792
1793
		$this->_lastAction = $formUrl;
0 ignored issues
show
Documentation Bug introduced by
It seems like $formUrl can also be of type array or boolean. However, the property $_lastAction is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
1794
1795
		$out = $this->Html->useTag('form', $formUrl, $formOptions);
1796
		$out .= $this->Html->useTag('hidden', '_method', array(
1797
			'value' => $requestMethod
1798
		));
1799
		$out .= $this->_csrfField();
1800
1801
		$fields = array();
1802
		if (isset($options['data']) && is_array($options['data'])) {
1803
			foreach (Hash::flatten($options['data']) as $key => $value) {
1804
				$fields[$key] = $value;
1805
				$out .= $this->hidden($key, array('value' => $value, 'id' => false));
1806
			}
1807
			unset($options['data']);
1808
		}
1809
		$out .= $this->secure($fields);
1810
		$out .= $this->Html->useTag('formend');
1811
1812
		$url = '#';
1813
		$onClick = 'document.' . $formName . '.submit();';
1814
		if ($confirmMessage) {
1815
			$options['onclick'] = $this->_confirm($confirmMessage, $onClick, '', $options);
1816
		} else {
1817
			$options['onclick'] = $onClick . ' ';
1818
		}
1819
		$options['onclick'] .= 'event.returnValue = false; return false;';
1820
1821
		$out .= $this->Html->link($title, $url, $options);
1822
		return $out;
1823
	}
1824
1825
/**
1826
 * Creates a submit button element. This method will generate `<input />` elements that
1827
 * can be used to submit, and reset forms by using $options. image submits can be created by supplying an
1828
 * image path for $caption.
1829
 *
1830
 * ### Options
1831
 *
1832
 * - `div` - Include a wrapping div?  Defaults to true. Accepts sub options similar to
1833
 *   FormHelper::input().
1834
 * - `before` - Content to include before the input.
1835
 * - `after` - Content to include after the input.
1836
 * - `type` - Set to 'reset' for reset inputs. Defaults to 'submit'
1837
 * - Other attributes will be assigned to the input element.
1838
 *
1839
 * ### Options
1840
 *
1841
 * - `div` - Include a wrapping div?  Defaults to true. Accepts sub options similar to
1842
 *   FormHelper::input().
1843
 * - Other attributes will be assigned to the input element.
1844
 *
1845
 * @param string $caption The label appearing on the button OR if string contains :// or the
1846
 *  extension .jpg, .jpe, .jpeg, .gif, .png use an image if the extension
1847
 *  exists, AND the first character is /, image is relative to webroot,
1848
 *  OR if the first character is not /, image is relative to webroot/img.
1849
 * @param array $options Array of options. See above.
1850
 * @return string A HTML submit button
1851
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::submit
1852
 */
1853
	public function submit($caption = null, $options = array()) {
1854
		if (!is_string($caption) && empty($caption)) {
1855
			$caption = __d('cake', 'Submit');
1856
		}
1857
		$out = null;
1858
		$div = true;
1859
1860
		if (isset($options['div'])) {
1861
			$div = $options['div'];
1862
			unset($options['div']);
1863
		}
1864
		$options += array('type' => 'submit', 'before' => null, 'after' => null, 'secure' => false);
1865
		$divOptions = array('tag' => 'div');
1866
1867
		if ($div === true) {
1868
			$divOptions['class'] = 'submit';
1869
		} elseif ($div === false) {
1870
			unset($divOptions);
1871
		} elseif (is_string($div)) {
1872
			$divOptions['class'] = $div;
1873
		} elseif (is_array($div)) {
1874
			$divOptions = array_merge(array('class' => 'submit', 'tag' => 'div'), $div);
1875
		}
1876
1877 View Code Duplication
		if (isset($options['name'])) {
1878
			$name = str_replace(array('[', ']'), array('.', ''), $options['name']);
1879
			$this->_secure($options['secure'], $name);
1880
		}
1881
		unset($options['secure']);
1882
1883
		$before = $options['before'];
1884
		$after = $options['after'];
1885
		unset($options['before'], $options['after']);
1886
1887
		$isUrl = strpos($caption, '://') !== false;
1888
		$isImage = preg_match('/\.(jpg|jpe|jpeg|gif|png|ico)$/', $caption);
1889
1890
		if ($isUrl || $isImage) {
1891
			$unlockFields = array('x', 'y');
1892
			if (isset($options['name'])) {
1893
				$unlockFields = array(
1894
					$options['name'] . '_x', $options['name'] . '_y'
1895
				);
1896
			}
1897
			foreach ($unlockFields as $ignore) {
1898
				$this->unlockField($ignore);
1899
			}
1900
		}
1901
1902
		if ($isUrl) {
1903
			unset($options['type']);
1904
			$tag = $this->Html->useTag('submitimage', $caption, $options);
1905
		} elseif ($isImage) {
1906
			unset($options['type']);
1907
			if ($caption{0} !== '/') {
1908
				$url = $this->webroot(Configure::read('App.imageBaseUrl') . $caption);
1909
			} else {
1910
				$url = $this->webroot(trim($caption, '/'));
1911
			}
1912
			$url = $this->assetTimestamp($url);
1913
			$tag = $this->Html->useTag('submitimage', $url, $options);
1914
		} else {
1915
			$options['value'] = $caption;
1916
			$tag = $this->Html->useTag('submit', $options);
1917
		}
1918
		$out = $before . $tag . $after;
1919
1920
		if (isset($divOptions)) {
1921
			$tag = $divOptions['tag'];
1922
			unset($divOptions['tag']);
1923
			$out = $this->Html->tag($tag, $out, $divOptions);
1924
		}
1925
		return $out;
1926
	}
1927
1928
/**
1929
 * Returns a formatted SELECT element.
1930
 *
1931
 * ### Attributes:
1932
 *
1933
 * - `showParents` - If included in the array and set to true, an additional option element
1934
 *   will be added for the parent of each option group. You can set an option with the same name
1935
 *   and it's key will be used for the value of the option.
1936
 * - `multiple` - show a multiple select box. If set to 'checkbox' multiple checkboxes will be
1937
 *   created instead.
1938
 * - `empty` - If true, the empty select option is shown. If a string,
1939
 *   that string is displayed as the empty element.
1940
 * - `escape` - If true contents of options will be HTML entity encoded. Defaults to true.
1941
 * - `value` The selected value of the input.
1942
 * - `class` - When using multiple = checkbox the class name to apply to the divs. Defaults to 'checkbox'.
1943
 * - `disabled` - Control the disabled attribute. When creating a select box, set to true to disable the
1944
 *   select box. When creating checkboxes, `true` will disable all checkboxes. You can also set disabled
1945
 *   to a list of values you want to disable when creating checkboxes.
1946
 *
1947
 * ### Using options
1948
 *
1949
 * A simple array will create normal options:
1950
 *
1951
 * {{{
1952
 * $options = array(1 => 'one', 2 => 'two);
1953
 * $this->Form->select('Model.field', $options));
1954
 * }}}
1955
 *
1956
 * While a nested options array will create optgroups with options inside them.
1957
 * {{{
1958
 * $options = array(
1959
 *  1 => 'bill',
1960
 *  'fred' => array(
1961
 *     2 => 'fred',
1962
 *     3 => 'fred jr.'
1963
 *  )
1964
 * );
1965
 * $this->Form->select('Model.field', $options);
1966
 * }}}
1967
 *
1968
 * In the above `2 => 'fred'` will not generate an option element. You should enable the `showParents`
1969
 * attribute to show the fred option.
1970
 *
1971
 * If you have multiple options that need to have the same value attribute, you can
1972
 * use an array of arrays to express this:
1973
 *
1974
 * {{{
1975
 * $options = array(
1976
 *  array('name' => 'United states', 'value' => 'USA'),
1977
 *  array('name' => 'USA', 'value' => 'USA'),
1978
 * );
1979
 * }}}
1980
 *
1981
 * @param string $fieldName Name attribute of the SELECT
1982
 * @param array $options Array of the OPTION elements (as 'value'=>'Text' pairs) to be used in the
1983
 *	SELECT element
1984
 * @param array $attributes The HTML attributes of the select element.
1985
 * @return string Formatted SELECT element
1986
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#options-for-select-checkbox-and-radio-inputs
1987
 */
1988
	public function select($fieldName, $options = array(), $attributes = array()) {
1989
		$select = array();
1990
		$style = null;
1991
		$tag = null;
1992
		$attributes += array(
1993
			'class' => null,
1994
			'escape' => true,
1995
			'secure' => true,
1996
			'empty' => '',
1997
			'showParents' => false,
1998
			'hiddenField' => true,
1999
			'disabled' => false
2000
		);
2001
2002
		$escapeOptions = $this->_extractOption('escape', $attributes);
2003
		$secure = $this->_extractOption('secure', $attributes);
2004
		$showEmpty = $this->_extractOption('empty', $attributes);
2005
		$showParents = $this->_extractOption('showParents', $attributes);
2006
		$hiddenField = $this->_extractOption('hiddenField', $attributes);
2007
		unset($attributes['escape'], $attributes['secure'], $attributes['empty'], $attributes['showParents'], $attributes['hiddenField']);
2008
		$id = $this->_extractOption('id', $attributes);
2009
2010
		$attributes = $this->_initInputField($fieldName, array_merge(
2011
			(array)$attributes, array('secure' => self::SECURE_SKIP)
2012
		));
2013
2014
		if (is_string($options) && isset($this->_options[$options])) {
2015
			$options = $this->_generateOptions($options);
2016
		} elseif (!is_array($options)) {
2017
			$options = array();
2018
		}
2019
		if (isset($attributes['type'])) {
2020
			unset($attributes['type']);
2021
		}
2022
2023
		if (!empty($attributes['multiple'])) {
2024
			$style = ($attributes['multiple'] === 'checkbox') ? 'checkbox' : null;
2025
			$template = ($style) ? 'checkboxmultiplestart' : 'selectmultiplestart';
2026
			$tag = $template;
2027
			if ($hiddenField) {
2028
				$hiddenAttributes = array(
2029
					'value' => '',
2030
					'id' => $attributes['id'] . ($style ? '' : '_'),
2031
					'secure' => false,
2032
					'form' => isset($attributes['form']) ? $attributes['form'] : null,
2033
					'name' => $attributes['name']
2034
				);
2035
				$select[] = $this->hidden(null, $hiddenAttributes);
2036
			}
2037
		} else {
2038
			$tag = 'selectstart';
2039
		}
2040
2041
		if ($tag === 'checkboxmultiplestart') {
2042
			unset($attributes['required']);
2043
		}
2044
2045
		if (!empty($tag) || isset($template)) {
2046
			$hasOptions = (count($options) > 0 || $showEmpty);
2047
			// Secure the field if there are options, or its a multi select.
2048
			// Single selects with no options don't submit, but multiselects do.
2049
			if (
2050
				(!isset($secure) || $secure) &&
2051
				empty($attributes['disabled']) &&
2052
				(!empty($attributes['multiple']) || $hasOptions)
2053
			) {
2054
				$this->_secure(true, $this->_secureFieldName($attributes));
0 ignored issues
show
Bug introduced by
It seems like $this->_secureFieldName($attributes) targeting FormHelper::_secureFieldName() can also be of type array<integer,string>; however, FormHelper::_secure() does only seem to accept string|null, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2055
			}
2056
			$select[] = $this->Html->useTag($tag, $attributes['name'], array_diff_key($attributes, array('name' => null, 'value' => null)));
2057
		}
2058
		$emptyMulti = (
2059
			$showEmpty !== null && $showEmpty !== false && !(
2060
				empty($showEmpty) && (isset($attributes) &&
2061
				array_key_exists('multiple', $attributes))
2062
			)
2063
		);
2064
2065
		if ($emptyMulti) {
2066
			$showEmpty = ($showEmpty === true) ? '' : $showEmpty;
2067
			$options = array('' => $showEmpty) + $options;
2068
		}
2069
2070
		if (!$id) {
2071
			$attributes['id'] = Inflector::camelize($attributes['id']);
2072
		}
2073
2074
		$select = array_merge($select, $this->_selectOptions(
2075
			array_reverse($options, true),
2076
			array(),
2077
			$showParents,
2078
			array(
2079
				'escape' => $escapeOptions,
2080
				'style' => $style,
2081
				'name' => $attributes['name'],
2082
				'value' => $attributes['value'],
2083
				'class' => $attributes['class'],
2084
				'id' => $attributes['id'],
2085
				'disabled' => $attributes['disabled'],
2086
			)
2087
		));
2088
2089
		$template = ($style === 'checkbox') ? 'checkboxmultipleend' : 'selectend';
2090
		$select[] = $this->Html->useTag($template);
2091
		return implode("\n", $select);
2092
	}
2093
2094
/**
2095
 * Generates a valid DOM ID suffix from a string.
2096
 * Also avoids collisions when multiple values are coverted to the same suffix by
2097
 * appending a numeric value.
2098
 *
2099
 * For pre-HTML5 IDs only characters like a-z 0-9 - _ are valid. HTML5 doesn't have that
2100
 * limitation, but to avoid layout issues it still filters out some sensitive chars.
2101
 *
2102
 * @param string $value The value that should be transferred into a DOM ID suffix.
2103
 * @param string $type Doctype to use. Defaults to html4.
2104
 * @return string DOM ID
2105
 */
2106
	public function domIdSuffix($value, $type = 'html4') {
2107
		if ($type === 'html5') {
2108
			$value = str_replace(array('@', '<', '>', ' ', '"', '\''), '_', $value);
2109
		} else {
2110
			$value = Inflector::camelize(Inflector::slug($value));
2111
		}
2112
		$value = Inflector::camelize($value);
2113
		$count = 1;
2114
		$suffix = $value;
2115
		while (in_array($suffix, $this->_domIdSuffixes)) {
2116
			$suffix = $value . $count++;
2117
		}
2118
		$this->_domIdSuffixes[] = $suffix;
2119
		return $suffix;
2120
	}
2121
2122
/**
2123
 * Returns a SELECT element for days.
2124
 *
2125
 * ### Attributes:
2126
 *
2127
 * - `empty` - If true, the empty select option is shown. If a string,
2128
 *   that string is displayed as the empty element.
2129
 * - `value` The selected value of the input.
2130
 *
2131
 * @param string $fieldName Prefix name for the SELECT element
2132
 * @param array $attributes HTML attributes for the select element
2133
 * @return string A generated day select box.
2134
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::day
2135
 */
2136
	public function day($fieldName = null, $attributes = array()) {
2137
		$attributes += array('empty' => true, 'value' => null);
2138
		$attributes = $this->_dateTimeSelected('day', $fieldName, $attributes);
2139
2140 View Code Duplication
		if (strlen($attributes['value']) > 2) {
2141
			$date = date_create($attributes['value']);
2142
			$attributes['value'] = null;
2143
			if ($date) {
2144
				$attributes['value'] = $date->format('d');
2145
			}
2146
		} elseif ($attributes['value'] === false) {
2147
			$attributes['value'] = null;
2148
		}
2149
		return $this->select($fieldName . ".day", $this->_generateOptions('day'), $attributes);
2150
	}
2151
2152
/**
2153
 * Returns a SELECT element for years
2154
 *
2155
 * ### Attributes:
2156
 *
2157
 * - `empty` - If true, the empty select option is shown. If a string,
2158
 *   that string is displayed as the empty element.
2159
 * - `orderYear` - Ordering of year values in select options.
2160
 *   Possible values 'asc', 'desc'. Default 'desc'
2161
 * - `value` The selected value of the input.
2162
 *
2163
 * @param string $fieldName Prefix name for the SELECT element
2164
 * @param integer $minYear First year in sequence
2165
 * @param integer $maxYear Last year in sequence
2166
 * @param array $attributes Attribute array for the select elements.
2167
 * @return string Completed year select input
2168
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::year
2169
 */
2170
	public function year($fieldName, $minYear = null, $maxYear = null, $attributes = array()) {
2171
		$attributes += array('empty' => true, 'value' => null);
2172
		if ((empty($attributes['value']) || $attributes['value'] === true) && $value = $this->value($fieldName)) {
2173
			if (is_array($value)) {
2174
				$year = null;
2175
				extract($value);
2176
				$attributes['value'] = $year;
2177
			} else {
2178
				if (empty($value)) {
2179
					if (!$attributes['empty'] && !$maxYear) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $maxYear of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

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

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
2180
						$attributes['value'] = 'now';
2181
2182
					} elseif (!$attributes['empty'] && $maxYear && !$attributes['value']) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $maxYear of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

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

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
2183
						$attributes['value'] = $maxYear;
2184
					}
2185
				} else {
2186
					$attributes['value'] = $value;
2187
				}
2188
			}
2189
		}
2190
2191 View Code Duplication
		if (strlen($attributes['value']) > 4 || $attributes['value'] === 'now') {
2192
			$date = date_create($attributes['value']);
2193
			$attributes['value'] = null;
2194
			if ($date) {
2195
				$attributes['value'] = $date->format('Y');
2196
			}
2197
		} elseif ($attributes['value'] === false) {
2198
			$attributes['value'] = null;
2199
		}
2200
		$yearOptions = array('value' => $attributes['value'], 'min' => $minYear, 'max' => $maxYear, 'order' => 'desc');
2201
		if (isset($attributes['orderYear'])) {
2202
			$yearOptions['order'] = $attributes['orderYear'];
2203
			unset($attributes['orderYear']);
2204
		}
2205
		return $this->select(
2206
			$fieldName . '.year', $this->_generateOptions('year', $yearOptions),
2207
			$attributes
2208
		);
2209
	}
2210
2211
/**
2212
 * Returns a SELECT element for months.
2213
 *
2214
 * ### Attributes:
2215
 *
2216
 * - `monthNames` - If false, 2 digit numbers will be used instead of text.
2217
 *   If a array, the given array will be used.
2218
 * - `empty` - If true, the empty select option is shown. If a string,
2219
 *   that string is displayed as the empty element.
2220
 * - `value` The selected value of the input.
2221
 *
2222
 * @param string $fieldName Prefix name for the SELECT element
2223
 * @param array $attributes Attributes for the select element
2224
 * @return string A generated month select dropdown.
2225
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::month
2226
 */
2227
	public function month($fieldName, $attributes = array()) {
2228
		$attributes += array('empty' => true, 'value' => null);
2229
		$attributes = $this->_dateTimeSelected('month', $fieldName, $attributes);
2230
2231 View Code Duplication
		if (strlen($attributes['value']) > 2) {
2232
			$date = date_create($attributes['value']);
2233
			$attributes['value'] = null;
2234
			if ($date) {
2235
				$attributes['value'] = $date->format('m');
2236
			}
2237
		} elseif ($attributes['value'] === false) {
2238
			$attributes['value'] = null;
2239
		}
2240
		$defaults = array('monthNames' => true);
2241
		$attributes = array_merge($defaults, (array)$attributes);
2242
		$monthNames = $attributes['monthNames'];
2243
		unset($attributes['monthNames']);
2244
2245
		return $this->select(
2246
			$fieldName . ".month",
2247
			$this->_generateOptions('month', array('monthNames' => $monthNames)),
2248
			$attributes
2249
		);
2250
	}
2251
2252
/**
2253
 * Returns a SELECT element for hours.
2254
 *
2255
 * ### Attributes:
2256
 *
2257
 * - `empty` - If true, the empty select option is shown. If a string,
2258
 *   that string is displayed as the empty element.
2259
 * - `value` The selected value of the input.
2260
 *
2261
 * @param string $fieldName Prefix name for the SELECT element
2262
 * @param boolean $format24Hours True for 24 hours format
2263
 * @param array $attributes List of HTML attributes
2264
 * @return string Completed hour select input
2265
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::hour
2266
 */
2267
	public function hour($fieldName, $format24Hours = false, $attributes = array()) {
2268
		$attributes += array('empty' => true, 'value' => null);
2269
		$attributes = $this->_dateTimeSelected('hour', $fieldName, $attributes);
2270
2271
		if (strlen($attributes['value']) > 2) {
2272
			try {
2273
				$date = new DateTime($attributes['value']);
2274
				if ($format24Hours) {
2275
					$attributes['value'] = $date->format('H');
2276
				} else {
2277
					$attributes['value'] = $date->format('g');
2278
				}
2279
			} catch (Exception $e) {
2280
				$attributes['value'] = null;
2281
			}
2282
		} elseif ($attributes['value'] === false) {
2283
			$attributes['value'] = null;
2284
		}
2285
2286
		if ($attributes['value'] > 12 && !$format24Hours) {
2287
			$attributes['value'] -= 12;
2288
		}
2289
		if (($attributes['value'] === 0 || $attributes['value'] === '00') && !$format24Hours) {
2290
			$attributes['value'] = 12;
2291
		}
2292
2293
		return $this->select(
2294
			$fieldName . ".hour",
2295
			$this->_generateOptions($format24Hours ? 'hour24' : 'hour'),
2296
			$attributes
2297
		);
2298
	}
2299
2300
/**
2301
 * Returns a SELECT element for minutes.
2302
 *
2303
 * ### Attributes:
2304
 *
2305
 * - `empty` - If true, the empty select option is shown. If a string,
2306
 *   that string is displayed as the empty element.
2307
 * - `value` The selected value of the input.
2308
 *
2309
 * @param string $fieldName Prefix name for the SELECT element
2310
 * @param array $attributes Array of Attributes
2311
 * @return string Completed minute select input.
2312
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::minute
2313
 */
2314
	public function minute($fieldName, $attributes = array()) {
2315
		$attributes += array('empty' => true, 'value' => null);
2316
		$attributes = $this->_dateTimeSelected('min', $fieldName, $attributes);
2317
2318 View Code Duplication
		if (strlen($attributes['value']) > 2) {
2319
			$date = date_create($attributes['value']);
2320
			$attributes['value'] = null;
2321
			if ($date) {
2322
				$attributes['value'] = $date->format('i');
2323
			}
2324
		} elseif ($attributes['value'] === false) {
2325
			$attributes['value'] = null;
2326
		}
2327
		$minuteOptions = array();
2328
2329
		if (isset($attributes['interval'])) {
2330
			$minuteOptions['interval'] = $attributes['interval'];
2331
			unset($attributes['interval']);
2332
		}
2333
		return $this->select(
2334
			$fieldName . ".min", $this->_generateOptions('minute', $minuteOptions),
2335
			$attributes
2336
		);
2337
	}
2338
2339
/**
2340
 * Selects values for dateTime selects.
2341
 *
2342
 * @param string $select Name of element field. ex. 'day'
2343
 * @param string $fieldName Name of fieldName being generated ex. Model.created
2344
 * @param array $attributes Array of attributes, must contain 'empty' key.
2345
 * @return array Attributes array with currently selected value.
2346
 */
2347
	protected function _dateTimeSelected($select, $fieldName, $attributes) {
2348
		if ((empty($attributes['value']) || $attributes['value'] === true) && $value = $this->value($fieldName)) {
2349
			if (is_array($value)) {
2350
				$attributes['value'] = isset($value[$select]) ? $value[$select] : null;
2351
			} else {
2352
				if (empty($value)) {
2353
					if (!$attributes['empty']) {
2354
						$attributes['value'] = 'now';
2355
					}
2356
				} else {
2357
					$attributes['value'] = $value;
2358
				}
2359
			}
2360
		}
2361
		return $attributes;
2362
	}
2363
2364
/**
2365
 * Returns a SELECT element for AM or PM.
2366
 *
2367
 * ### Attributes:
2368
 *
2369
 * - `empty` - If true, the empty select option is shown. If a string,
2370
 *   that string is displayed as the empty element.
2371
 * - `value` The selected value of the input.
2372
 *
2373
 * @param string $fieldName Prefix name for the SELECT element
2374
 * @param array $attributes Array of Attributes
2375
 * @return string Completed meridian select input
2376
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::meridian
2377
 */
2378
	public function meridian($fieldName, $attributes = array()) {
2379
		$attributes += array('empty' => true, 'value' => null);
2380
		if ((empty($attributes['value']) || $attributes['value'] === true) && $value = $this->value($fieldName)) {
2381
			if (is_array($value)) {
2382
				$meridian = null;
2383
				extract($value);
2384
				$attributes['value'] = $meridian;
2385
			} else {
2386
				if (empty($value)) {
2387
					if (!$attributes['empty']) {
2388
						$attributes['value'] = date('a');
2389
					}
2390
				} else {
2391
					$date = date_create($attributes['value']);
2392
					$attributes['value'] = null;
2393
					if ($date) {
2394
						$attributes['value'] = $date->format('a');
2395
					}
2396
				}
2397
			}
2398
		}
2399
2400
		if ($attributes['value'] === false) {
2401
			$attributes['value'] = null;
2402
		}
2403
		return $this->select(
2404
			$fieldName . ".meridian", $this->_generateOptions('meridian'),
2405
			$attributes
2406
		);
2407
	}
2408
2409
/**
2410
 * Returns a set of SELECT elements for a full datetime setup: day, month and year, and then time.
2411
 *
2412
 * ### Attributes:
2413
 *
2414
 * - `monthNames` If false, 2 digit numbers will be used instead of text.
2415
 *   If a array, the given array will be used.
2416
 * - `minYear` The lowest year to use in the year select
2417
 * - `maxYear` The maximum year to use in the year select
2418
 * - `interval` The interval for the minutes select. Defaults to 1
2419
 * - `separator` The contents of the string between select elements. Defaults to '-'
2420
 * - `empty` - If true, the empty select option is shown. If a string,
2421
 *   that string is displayed as the empty element.
2422
 * - `round` - Set to `up` or `down` if you want to force rounding in either direction. Defaults to null.
2423
 * - `value` | `default` The default value to be used by the input. A value in `$this->data`
2424
 *   matching the field name will override this value. If no default is provided `time()` will be used.
2425
 *
2426
 * @param string $fieldName Prefix name for the SELECT element
2427
 * @param string $dateFormat DMY, MDY, YMD, or null to not generate date inputs.
2428
 * @param string $timeFormat 12, 24, or null to not generate time inputs.
2429
 * @param array $attributes Array of Attributes
2430
 * @return string Generated set of select boxes for the date and time formats chosen.
2431
 * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::dateTime
2432
 */
2433
	public function dateTime($fieldName, $dateFormat = 'DMY', $timeFormat = '12', $attributes = array()) {
2434
		$attributes += array('empty' => true, 'value' => null);
2435
		$year = $month = $day = $hour = $min = $meridian = null;
2436
2437
		if (empty($attributes['value'])) {
2438
			$attributes = $this->value($attributes, $fieldName);
2439
		}
2440
2441
		if ($attributes['value'] === null && $attributes['empty'] != true) {
2442
			$attributes['value'] = time();
2443
			if (!empty($attributes['maxYear']) && $attributes['maxYear'] < date('Y')) {
2444
				$attributes['value'] = strtotime(date($attributes['maxYear'] . '-m-d'));
2445
			}
2446
		}
2447
2448
		if (!empty($attributes['value'])) {
2449
			list($year, $month, $day, $hour, $min, $meridian) = $this->_getDateTimeValue(
2450
				$attributes['value'],
2451
				$timeFormat
2452
			);
2453
		}
2454
2455
		$defaults = array(
2456
			'minYear' => null, 'maxYear' => null, 'separator' => '-',
2457
			'interval' => 1, 'monthNames' => true, 'round' => null
2458
		);
2459
		$attributes = array_merge($defaults, (array)$attributes);
2460
		if (isset($attributes['minuteInterval'])) {
2461
			$attributes['interval'] = $attributes['minuteInterval'];
2462
			unset($attributes['minuteInterval']);
2463
		}
2464
		$minYear = $attributes['minYear'];
2465
		$maxYear = $attributes['maxYear'];
2466
		$separator = $attributes['separator'];
2467
		$interval = $attributes['interval'];
2468
		$monthNames = $attributes['monthNames'];
2469
		$round = $attributes['round'];
2470
		$attributes = array_diff_key($attributes, $defaults);
2471
2472
		if (!empty($interval) && $interval > 1 && !empty($min)) {
2473
			$current = new DateTime();
2474
			if ($year !== null) {
2475
				$current->setDate($year, $month, $day);
2476
			}
2477
			if ($hour !== null) {
2478
				$current->setTime($hour, $min);
2479
			}
2480
			$changeValue = $min * (1 / $interval);
2481
			switch ($round) {
2482
				case 'up':
2483
					$changeValue = ceil($changeValue);
2484
					break;
2485
				case 'down':
2486
					$changeValue = floor($changeValue);
2487
					break;
2488
				default:
2489
					$changeValue = round($changeValue);
2490
			}
2491
			$change = ($changeValue * $interval) - $min;
2492
			$current->modify($change > 0 ? "+$change minutes" : "$change minutes");
2493
			$format = ($timeFormat == 12) ? 'Y m d h i a' : 'Y m d H i a';
2494
			$newTime = explode(' ', $current->format($format));
2495
			list($year, $month, $day, $hour, $min, $meridian) = $newTime;
2496
		}
2497
2498
		$keys = array('Day', 'Month', 'Year', 'Hour', 'Minute', 'Meridian');
2499
		$attrs = array_fill_keys($keys, $attributes);
2500
2501
		$hasId = isset($attributes['id']);
2502
		if ($hasId && is_array($attributes['id'])) {
2503
			// check for missing ones and build selectAttr for each element
2504
			$attributes['id'] += array(
2505
				'month' => '',
2506
				'year' => '',
2507
				'day' => '',
2508
				'hour' => '',
2509
				'minute' => '',
2510
				'meridian' => ''
2511
			);
2512 View Code Duplication
			foreach ($keys as $key) {
2513
				$attrs[$key]['id'] = $attributes['id'][strtolower($key)];
2514
			}
2515
		}
2516
		if ($hasId && is_string($attributes['id'])) {
2517
			// build out an array version
2518
			foreach ($keys as $key) {
2519
				$attrs[$key]['id'] = $attributes['id'] . $key;
2520
			}
2521
		}
2522
2523
		if (is_array($attributes['empty'])) {
2524
			$attributes['empty'] += array(
2525
				'month' => true,
2526
				'year' => true,
2527
				'day' => true,
2528
				'hour' => true,
2529
				'minute' => true,
2530
				'meridian' => true
2531
			);
2532 View Code Duplication
			foreach ($keys as $key) {
2533
				$attrs[$key]['empty'] = $attributes['empty'][strtolower($key)];
2534
			}
2535
		}
2536
2537
		$selects = array();
2538
		foreach (preg_split('//', $dateFormat, -1, PREG_SPLIT_NO_EMPTY) as $char) {
2539
			switch ($char) {
2540
				case 'Y':
2541
					$attrs['Year']['value'] = $year;
2542
					$selects[] = $this->year(
2543
						$fieldName, $minYear, $maxYear, $attrs['Year']
2544
					);
2545
					break;
2546
				case 'M':
2547
					$attrs['Month']['value'] = $month;
2548
					$attrs['Month']['monthNames'] = $monthNames;
2549
					$selects[] = $this->month($fieldName, $attrs['Month']);
2550
					break;
2551
				case 'D':
2552
					$attrs['Day']['value'] = $day;
2553
					$selects[] = $this->day($fieldName, $attrs['Day']);
2554
					break;
2555
			}
2556
		}
2557
		$opt = implode($separator, $selects);
2558
2559
		$attrs['Minute']['interval'] = $interval;
2560
		switch ($timeFormat) {
2561
			case '24':
2562
				$attrs['Hour']['value'] = $hour;
2563
				$attrs['Minute']['value'] = $min;
2564
				$opt .= $this->hour($fieldName, true, $attrs['Hour']) . ':' .
2565
				$this->minute($fieldName, $attrs['Minute']);
2566
				break;
2567
			case '12':
2568
				$attrs['Hour']['value'] = $hour;
2569
				$attrs['Minute']['value'] = $min;
2570
				$attrs['Meridian']['value'] = $meridian;
2571
				$opt .= $this->hour($fieldName, false, $attrs['Hour']) . ':' .
2572
				$this->minute($fieldName, $attrs['Minute']) . ' ' .
2573
				$this->meridian($fieldName, $attrs['Meridian']);
2574
				break;
2575
		}
2576
		return $opt;
2577
	}
2578
2579
/**
2580
 * Parse the value for a datetime selected value
2581
 *
2582
 * @param string|array $value The selected value.
2583
 * @param integer $timeFormat The time format
2584
 * @return array Array of selected value.
2585
 */
2586
	protected function _getDateTimeValue($value, $timeFormat) {
2587
		$year = $month = $day = $hour = $min = $meridian = null;
2588
		if (is_array($value)) {
2589
			extract($value);
2590
			if ($meridian === 'pm') {
2591
				$hour += 12;
2592
			}
2593
			return array($year, $month, $day, $hour, $min, $meridian);
2594
		}
2595
2596
		if (is_numeric($value)) {
2597
			$value = strftime('%Y-%m-%d %H:%M:%S', $value);
2598
		}
2599
		$meridian = 'am';
2600
		$pos = strpos($value, '-');
2601
		if ($pos !== false) {
2602
			$date = explode('-', $value);
2603
			$days = explode(' ', $date[2]);
2604
			$day = $days[0];
2605
			$month = $date[1];
2606
			$year = $date[0];
2607
		} else {
2608
			$days[1] = $value;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$days was never initialized. Although not strictly required by PHP, it is generally a good practice to add $days = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
2609
		}
2610
2611
		if (!empty($timeFormat)) {
2612
			$time = explode(':', $days[1]);
2613
2614
			if ($time[0] >= 12) {
2615
				$meridian = 'pm';
2616
			}
2617
			$hour = $min = null;
2618
			if (isset($time[1])) {
2619
				$hour = $time[0];
2620
				$min = $time[1];
2621
			}
2622
		}
2623
		return array($year, $month, $day, $hour, $min, $meridian);
2624
	}
2625
2626
/**
2627
 * Gets the input field name for the current tag
2628
 *
2629
 * @param array $options
2630
 * @param string $field
2631
 * @param string $key
2632
 * @return array
2633
 */
2634
	protected function _name($options = array(), $field = null, $key = 'name') {
2635
		if ($this->requestType === 'get') {
2636 View Code Duplication
			if ($options === null) {
2637
				$options = array();
2638
			} elseif (is_string($options)) {
2639
				$field = $options;
2640
				$options = 0;
2641
			}
2642
2643
			if (!empty($field)) {
2644
				$this->setEntity($field);
2645
			}
2646
2647
			if (is_array($options) && isset($options[$key])) {
2648
				return $options;
2649
			}
2650
2651
			$entity = $this->entity();
2652
			$model = $this->model();
2653
			$name = $model === $entity[0] && isset($entity[1]) ? $entity[1] : $entity[0];
2654
			$last = $entity[count($entity) - 1];
2655
			if (in_array($last, $this->_fieldSuffixes)) {
2656
				$name .= '[' . $last . ']';
2657
			}
2658
2659
			if (is_array($options)) {
2660
				$options[$key] = $name;
2661
				return $options;
2662
			}
2663
			return $name;
2664
		}
2665
		return parent::_name($options, $field, $key);
0 ignored issues
show
Bug Compatibility introduced by
The expression parent::_name($options, $field, $key); of type array|string|null adds the type string to the return on line 2665 which is incompatible with the return type documented by FormHelper::_name of type array.
Loading history...
2666
	}
2667
2668
/**
2669
 * Returns an array of formatted OPTION/OPTGROUP elements
2670
 *
2671
 * @param array $elements
2672
 * @param array $parents
2673
 * @param boolean $showParents
2674
 * @param array $attributes
2675
 * @return array
2676
 */
2677
	protected function _selectOptions($elements = array(), $parents = array(), $showParents = null, $attributes = array()) {
2678
		$select = array();
2679
		$attributes = array_merge(
2680
			array('escape' => true, 'style' => null, 'value' => null, 'class' => null),
2681
			$attributes
2682
		);
2683
		$selectedIsEmpty = ($attributes['value'] === '' || $attributes['value'] === null);
2684
		$selectedIsArray = is_array($attributes['value']);
2685
2686
		$this->_domIdSuffixes = array();
2687
		foreach ($elements as $name => $title) {
2688
			$htmlOptions = array();
2689
			if (is_array($title) && (!isset($title['name']) || !isset($title['value']))) {
2690
				if (!empty($name)) {
2691 View Code Duplication
					if ($attributes['style'] === 'checkbox') {
2692
						$select[] = $this->Html->useTag('fieldsetend');
2693
					} else {
2694
						$select[] = $this->Html->useTag('optiongroupend');
2695
					}
2696
					$parents[] = $name;
2697
				}
2698
				$select = array_merge($select, $this->_selectOptions(
2699
					$title, $parents, $showParents, $attributes
2700
				));
2701
2702
				if (!empty($name)) {
2703
					$name = $attributes['escape'] ? h($name) : $name;
2704 View Code Duplication
					if ($attributes['style'] === 'checkbox') {
2705
						$select[] = $this->Html->useTag('fieldsetstart', $name);
2706
					} else {
2707
						$select[] = $this->Html->useTag('optiongroup', $name, '');
2708
					}
2709
				}
2710
				$name = null;
2711
			} elseif (is_array($title)) {
2712
				$htmlOptions = $title;
2713
				$name = $title['value'];
2714
				$title = $title['name'];
2715
				unset($htmlOptions['name'], $htmlOptions['value']);
2716
			}
2717
2718
			if ($name !== null) {
2719
				$isNumeric = is_numeric($name);
2720
				if (
2721
					(!$selectedIsArray && !$selectedIsEmpty && (string)$attributes['value'] == (string)$name) ||
2722
					($selectedIsArray && in_array((string)$name, $attributes['value'], !$isNumeric))
2723
				) {
2724
					if ($attributes['style'] === 'checkbox') {
2725
						$htmlOptions['checked'] = true;
2726
					} else {
2727
						$htmlOptions['selected'] = 'selected';
2728
					}
2729
				}
2730
2731
				if ($showParents || (!in_array($title, $parents))) {
2732
					$title = ($attributes['escape']) ? h($title) : $title;
2733
2734
					$hasDisabled = !empty($attributes['disabled']);
2735
					if ($hasDisabled) {
2736
						$disabledIsArray = is_array($attributes['disabled']);
2737
						if ($disabledIsArray) {
2738
							$disabledIsNumeric = is_numeric($name);
2739
						}
2740
					}
2741
					if (
2742
						$hasDisabled &&
2743
						$disabledIsArray &&
0 ignored issues
show
Bug introduced by
The variable $disabledIsArray does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
2744
						in_array((string)$name, $attributes['disabled'], !$disabledIsNumeric)
0 ignored issues
show
Bug introduced by
The variable $disabledIsNumeric does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
2745
					) {
2746
						$htmlOptions['disabled'] = 'disabled';
2747
					}
2748
					if ($hasDisabled && !$disabledIsArray && $attributes['style'] === 'checkbox') {
2749
						$htmlOptions['disabled'] = $attributes['disabled'] === true ? 'disabled' : $attributes['disabled'];
2750
					}
2751
2752
					if ($attributes['style'] === 'checkbox') {
2753
						$htmlOptions['value'] = $name;
2754
2755
						$tagName = $attributes['id'] . $this->domIdSuffix($name);
2756
						$htmlOptions['id'] = $tagName;
2757
						$label = array('for' => $tagName);
2758
2759
						if (isset($htmlOptions['checked']) && $htmlOptions['checked'] === true) {
2760
							$label['class'] = 'selected';
2761
						}
2762
2763
						$name = $attributes['name'];
2764
2765
						if (empty($attributes['class'])) {
2766
							$attributes['class'] = 'checkbox';
2767
						} elseif ($attributes['class'] === 'form-error') {
2768
							$attributes['class'] = 'checkbox ' . $attributes['class'];
2769
						}
2770
						$label = $this->label(null, $title, $label);
2771
						$item = $this->Html->useTag('checkboxmultiple', $name, $htmlOptions);
2772
						$select[] = $this->Html->div($attributes['class'], $item . $label);
2773
					} else {
2774
						if ($attributes['escape']) {
2775
							$name = h($name);
2776
						}
2777
						$select[] = $this->Html->useTag('selectoption', $name, $htmlOptions, $title);
2778
					}
2779
				}
2780
			}
2781
		}
2782
2783
		return array_reverse($select, true);
2784
	}
2785
2786
/**
2787
 * Generates option lists for common <select /> menus
2788
 *
2789
 * @param string $name
2790
 * @param array $options
2791
 * @return array
2792
 */
2793
	protected function _generateOptions($name, $options = array()) {
2794
		if (!empty($this->options[$name])) {
2795
			return $this->options[$name];
2796
		}
2797
		$data = array();
2798
2799
		switch ($name) {
2800
			case 'minute':
2801
				if (isset($options['interval'])) {
2802
					$interval = $options['interval'];
2803
				} else {
2804
					$interval = 1;
2805
				}
2806
				$i = 0;
2807
				while ($i < 60) {
2808
					$data[sprintf('%02d', $i)] = sprintf('%02d', $i);
2809
					$i += $interval;
2810
				}
2811
				break;
2812
			case 'hour':
2813 View Code Duplication
				for ($i = 1; $i <= 12; $i++) {
2814
					$data[sprintf('%02d', $i)] = $i;
2815
				}
2816
				break;
2817
			case 'hour24':
2818 View Code Duplication
				for ($i = 0; $i <= 23; $i++) {
2819
					$data[sprintf('%02d', $i)] = $i;
2820
				}
2821
				break;
2822
			case 'meridian':
2823
				$data = array('am' => 'am', 'pm' => 'pm');
2824
				break;
2825
			case 'day':
2826
				$min = 1;
2827
				$max = 31;
2828
2829
				if (isset($options['min'])) {
2830
					$min = $options['min'];
2831
				}
2832
				if (isset($options['max'])) {
2833
					$max = $options['max'];
2834
				}
2835
2836 View Code Duplication
				for ($i = $min; $i <= $max; $i++) {
2837
					$data[sprintf('%02d', $i)] = $i;
2838
				}
2839
				break;
2840
			case 'month':
2841
				if ($options['monthNames'] === true) {
2842
					$data['01'] = __d('cake', 'January');
2843
					$data['02'] = __d('cake', 'February');
2844
					$data['03'] = __d('cake', 'March');
2845
					$data['04'] = __d('cake', 'April');
2846
					$data['05'] = __d('cake', 'May');
2847
					$data['06'] = __d('cake', 'June');
2848
					$data['07'] = __d('cake', 'July');
2849
					$data['08'] = __d('cake', 'August');
2850
					$data['09'] = __d('cake', 'September');
2851
					$data['10'] = __d('cake', 'October');
2852
					$data['11'] = __d('cake', 'November');
2853
					$data['12'] = __d('cake', 'December');
2854
				} elseif (is_array($options['monthNames'])) {
2855
					$data = $options['monthNames'];
2856
				} else {
2857
					for ($m = 1; $m <= 12; $m++) {
2858
						$data[sprintf("%02s", $m)] = strftime("%m", mktime(1, 1, 1, $m, 1, 1999));
2859
					}
2860
				}
2861
				break;
2862
			case 'year':
2863
				$current = intval(date('Y'));
2864
2865
				$min = !isset($options['min']) ? $current - 20 : (int)$options['min'];
2866
				$max = !isset($options['max']) ? $current + 20 : (int)$options['max'];
2867
2868
				if ($min > $max) {
2869
					list($min, $max) = array($max, $min);
2870
				}
2871
				if (
2872
					!empty($options['value']) &&
2873
					(int)$options['value'] < $min &&
2874
					(int)$options['value'] > 0
2875
				) {
2876
					$min = (int)$options['value'];
2877
				} elseif (!empty($options['value']) && (int)$options['value'] > $max) {
2878
					$max = (int)$options['value'];
2879
				}
2880
2881
				for ($i = $min; $i <= $max; $i++) {
2882
					$data[$i] = $i;
2883
				}
2884
				if ($options['order'] !== 'asc') {
2885
					$data = array_reverse($data, true);
2886
				}
2887
				break;
2888
		}
2889
		$this->_options[$name] = $data;
2890
		return $this->_options[$name];
2891
	}
2892
2893
/**
2894
 * Sets field defaults and adds field to form security input hash.
2895
 * Will also add a 'form-error' class if the field contains validation errors.
2896
 *
2897
 * ### Options
2898
 *
2899
 * - `secure` - boolean whether or not the field should be added to the security fields.
2900
 *   Disabling the field using the `disabled` option, will also omit the field from being
2901
 *   part of the hashed key.
2902
 *
2903
 * This method will convert a numerically indexed 'disabled' into a associative
2904
 * value. FormHelper's internals expect associative options.
2905
 *
2906
 * @param string $field Name of the field to initialize options for.
2907
 * @param array $options Array of options to append options into.
2908
 * @return array Array of options for the input.
2909
 */
2910
	protected function _initInputField($field, $options = array()) {
2911
		if (isset($options['secure'])) {
2912
			$secure = $options['secure'];
2913
			unset($options['secure']);
2914
		} else {
2915
			$secure = (isset($this->request['_Token']) && !empty($this->request['_Token']));
2916
		}
2917
2918
		$disabledIndex = array_search('disabled', $options, true);
2919
		if (is_int($disabledIndex)) {
2920
			unset($options[$disabledIndex]);
2921
			$options['disabled'] = true;
2922
		}
2923
2924
		$result = parent::_initInputField($field, $options);
2925
		if ($this->tagIsInvalid() !== false) {
2926
			$result = $this->addClass($result, 'form-error');
2927
		}
2928
2929
		if (!empty($result['disabled'])) {
2930
			return $result;
2931
		}
2932
2933
		if (!isset($result['required']) &&
2934
			$this->_introspectModel($this->model(), 'validates', $this->field())
2935
		) {
2936
			$result['required'] = true;
2937
		}
2938
2939
		if ($secure === self::SECURE_SKIP) {
2940
			return $result;
2941
		}
2942
2943
		$this->_secure($secure, $this->_secureFieldName($options));
0 ignored issues
show
Bug introduced by
It seems like $this->_secureFieldName($options) targeting FormHelper::_secureFieldName() can also be of type array<integer,string>; however, FormHelper::_secure() does only seem to accept string|null, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2944
		return $result;
2945
	}
2946
2947
/**
2948
 * Get the field name for use with _secure().
2949
 *
2950
 * Parses the name attribute to create a dot separated name value for use
2951
 * in secured field hash.
2952
 *
2953
 * @param array $options An array of options possibly containing a name key.
2954
 * @return string|null
2955
 */
2956
	protected function _secureFieldName($options) {
2957
		if (isset($options['name'])) {
2958
			preg_match_all('/\[(.*?)\]/', $options['name'], $matches);
2959
			if (isset($matches[1])) {
2960
				return $matches[1];
2961
			}
2962
		}
2963
		return null;
2964
	}
2965
2966
/**
2967
 * Set/Get inputDefaults for form elements
2968
 *
2969
 * @param array $defaults New default values
2970
 * @param boolean Merge with current defaults
2971
 * @return array inputDefaults
2972
 */
2973
	public function inputDefaults($defaults = null, $merge = false) {
2974
		if ($defaults !== null) {
2975
			if ($merge) {
2976
				$this->_inputDefaults = array_merge($this->_inputDefaults, (array)$defaults);
2977
			} else {
2978
				$this->_inputDefaults = (array)$defaults;
2979
			}
2980
		}
2981
		return $this->_inputDefaults;
2982
	}
2983
2984
}
2985