CakePHP
  • Documentation
    • Book
    • API
    • Videos
    • Reporting Security Issues
    • Privacy Policy
    • Logos & Trademarks
  • Business Solutions
  • Swag
  • Road Trip
  • Team
  • Community
    • Community
    • Get Involved
    • Issues (GitHub)
    • Bakery
    • Featured Resources
    • Training
    • Meetups
    • My CakePHP
    • CakeFest
    • Newsletter
    • Linkedin
    • YouTube
    • Facebook
    • Twitter
    • Mastodon
    • Help & Support
    • Forum
    • Stack Overflow
    • Slack
    • Paid Support
CakePHP

C CakePHP 2.4 API

  • Overview
  • Tree
  • Deprecated
  • Version:
    • 2.4
      • 4.2
      • 4.1
      • 4.0
      • 3.9
      • 3.8
      • 3.7
      • 3.6
      • 3.5
      • 3.4
      • 3.3
      • 3.2
      • 3.1
      • 3.0
      • 2.10
      • 2.9
      • 2.8
      • 2.7
      • 2.6
      • 2.5
      • 2.4
      • 2.3
      • 2.2
      • 2.1
      • 2.0
      • 1.3
      • 1.2

Packages

  • Cake
    • Cache
      • Engine
    • Configure
    • Console
      • Command
        • Task
    • Controller
      • Component
        • Acl
        • Auth
    • Core
    • Error
    • Event
    • I18n
    • Log
      • Engine
    • Model
      • Behavior
      • Datasource
        • Database
        • Session
      • Validator
    • Network
      • Email
      • Http
    • Routing
      • Filter
      • Route
    • TestSuite
      • Coverage
      • Fixture
      • Reporter
    • Utility
    • View
      • Helper

Classes

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

Generated using CakePHP API Docs