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