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

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

Packages

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

Classes

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