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