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