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.9 API

  • Overview
  • Tree
  • Deprecated
  • Version:
    • 2.9
      • 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
  • None

Classes

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