cake/libs/view/helpers/form.php

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