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