1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10: 11:
12:
13:
14:
15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38:
39: class Form extends FormContainer
40: {
41:
42: const EQUAL = ':equal',
43: IS_IN = ':equal',
44: FILLED = ':filled',
45: VALID = ':valid';
46:
47:
48: const PROTECTION = 'HiddenField::validateEqual';
49:
50:
51: const SUBMITTED = ':submitted';
52:
53:
54: const MIN_LENGTH = ':minLength',
55: MAX_LENGTH = ':maxLength',
56: LENGTH = ':length',
57: EMAIL = ':email',
58: URL = ':url',
59: REGEXP = ':regexp',
60: PATTERN = ':pattern',
61: INTEGER = ':integer',
62: NUMERIC = ':integer',
63: FLOAT = ':float',
64: RANGE = ':range';
65:
66:
67: const MAX_FILE_SIZE = ':fileSize',
68: MIME_TYPE = ':mimeType',
69: IMAGE = ':image';
70:
71:
72: const GET = 'get',
73: POST = 'post';
74:
75:
76: const TRACKER_ID = '_form_';
77:
78:
79: const PROTECTOR_ID = '_token_';
80:
81:
82: public $onSuccess;
83:
84:
85: public $onError;
86:
87:
88: public $onSubmit;
89:
90:
91: public $onInvalidSubmit;
92:
93:
94: private $submittedBy;
95:
96:
97: private $httpData;
98:
99:
100: private $element;
101:
102:
103: private $renderer;
104:
105:
106: private $translator;
107:
108:
109: private $groups = array();
110:
111:
112: private $errors = array();
113:
114:
115:
116: 117: 118: 119:
120: public function __construct($name = NULL)
121: {
122: $this->element = Html::el('form');
123: $this->element->action = '';
124: $this->element->method = self::POST;
125: $this->element->id = 'frm-' . $name;
126:
127: $this->monitor(__CLASS__);
128: if ($name !== NULL) {
129: $tracker = new HiddenField($name);
130: $tracker->unmonitor(__CLASS__);
131: $this[self::TRACKER_ID] = $tracker;
132: }
133: parent::__construct(NULL, $name);
134: }
135:
136:
137:
138: 139: 140: 141: 142: 143:
144: protected function attached($obj)
145: {
146: if ($obj instanceof self) {
147: throw new InvalidStateException('Nested forms are forbidden.');
148: }
149: }
150:
151:
152:
153: 154: 155: 156:
157: final public function getForm($need = TRUE)
158: {
159: return $this;
160: }
161:
162:
163:
164: 165: 166: 167: 168:
169: public function setAction($url)
170: {
171: $this->element->action = $url;
172: return $this;
173: }
174:
175:
176:
177: 178: 179: 180:
181: public function getAction()
182: {
183: return $this->element->action;
184: }
185:
186:
187:
188: 189: 190: 191: 192:
193: public function setMethod($method)
194: {
195: if ($this->httpData !== NULL) {
196: throw new InvalidStateException(__METHOD__ . '() must be called until the form is empty.');
197: }
198: $this->element->method = strtolower($method);
199: return $this;
200: }
201:
202:
203:
204: 205: 206: 207:
208: public function getMethod()
209: {
210: return $this->element->method;
211: }
212:
213:
214:
215: 216: 217: 218: 219: 220:
221: public function addProtection($message = NULL, $timeout = NULL)
222: {
223: $session = $this->getSession()->getSection('Nette.Forms.Form/CSRF');
224: $key = "key$timeout";
225: if (isset($session->$key)) {
226: $token = $session->$key;
227: } else {
228: $session->$key = $token = Strings::random();
229: }
230: $session->setExpiration($timeout, $key);
231: $this[self::PROTECTOR_ID] = new HiddenField($token);
232: $this[self::PROTECTOR_ID]->addRule(self::PROTECTION, $message, $token);
233: }
234:
235:
236:
237: 238: 239: 240: 241: 242:
243: public function addGroup($caption = NULL, $setAsCurrent = TRUE)
244: {
245: $group = new FormGroup;
246: $group->setOption('label', $caption);
247: $group->setOption('visual', TRUE);
248:
249: if ($setAsCurrent) {
250: $this->setCurrentGroup($group);
251: }
252:
253: if (isset($this->groups[$caption])) {
254: return $this->groups[] = $group;
255: } else {
256: return $this->groups[$caption] = $group;
257: }
258: }
259:
260:
261:
262: 263: 264: 265: 266:
267: public function removeGroup($name)
268: {
269: if (is_string($name) && isset($this->groups[$name])) {
270: $group = $this->groups[$name];
271:
272: } elseif ($name instanceof FormGroup && in_array($name, $this->groups, TRUE)) {
273: $group = $name;
274: $name = array_search($group, $this->groups, TRUE);
275:
276: } else {
277: throw new InvalidArgumentException("Group not found in form '$this->name'");
278: }
279:
280: foreach ($group->getControls() as $control) {
281: $this->removeComponent($control);
282: }
283:
284: unset($this->groups[$name]);
285: }
286:
287:
288:
289: 290: 291: 292:
293: public function getGroups()
294: {
295: return $this->groups;
296: }
297:
298:
299:
300: 301: 302: 303: 304:
305: public function getGroup($name)
306: {
307: return isset($this->groups[$name]) ? $this->groups[$name] : NULL;
308: }
309:
310:
311:
312:
313:
314:
315:
316: 317: 318: 319: 320:
321: public function setTranslator(ITranslator $translator = NULL)
322: {
323: $this->translator = $translator;
324: return $this;
325: }
326:
327:
328:
329: 330: 331: 332:
333: final public function getTranslator()
334: {
335: return $this->translator;
336: }
337:
338:
339:
340:
341:
342:
343:
344: 345: 346: 347:
348: public function isAnchored()
349: {
350: return TRUE;
351: }
352:
353:
354:
355: 356: 357: 358:
359: final public function isSubmitted()
360: {
361: if ($this->submittedBy === NULL) {
362: $this->getHttpData();
363: $this->submittedBy = !empty($this->httpData);
364: }
365: return $this->submittedBy;
366: }
367:
368:
369:
370: 371: 372: 373: 374:
375: public function setSubmittedBy(ISubmitterControl $by = NULL)
376: {
377: $this->submittedBy = $by === NULL ? FALSE : $by;
378: return $this;
379: }
380:
381:
382:
383: 384: 385: 386:
387: final public function getHttpData()
388: {
389: if ($this->httpData === NULL) {
390: if (!$this->isAnchored()) {
391: throw new InvalidStateException('Form is not anchored and therefore can not determine whether it was submitted.');
392: }
393: $this->httpData = (array) $this->receiveHttpData();
394: }
395: return $this->httpData;
396: }
397:
398:
399:
400: 401: 402: 403:
404: public function fireEvents()
405: {
406: if (!$this->isSubmitted()) {
407: return;
408:
409: } elseif ($this->submittedBy instanceof ISubmitterControl) {
410: if (!$this->submittedBy->getValidationScope() || $this->isValid()) {
411: $this->submittedBy->click();
412: $valid = TRUE;
413: } else {
414: $this->submittedBy->onInvalidClick($this->submittedBy);
415: }
416: }
417:
418: if (isset($valid) || $this->isValid()) {
419: $this->onSuccess($this);
420: } else {
421: $this->onError($this);
422: if ($this->onInvalidSubmit) {
423: trigger_error(__CLASS__ . '->onInvalidSubmit is deprecated; use onError instead.', E_USER_WARNING);
424: $this->onInvalidSubmit($this);
425: }
426: }
427:
428: if ($this->onSuccess) {
429: $this->onSubmit($this);
430: } elseif ($this->onSubmit) {
431: trigger_error(__CLASS__ . '->onSubmit changed its behavior; use onSuccess instead.', E_USER_WARNING);
432: if (isset($valid) || $this->isValid()) {
433: $this->onSubmit($this);
434: }
435: }
436: }
437:
438:
439:
440: 441: 442: 443:
444: protected function receiveHttpData()
445: {
446: $httpRequest = $this->getHttpRequest();
447: if (strcasecmp($this->getMethod(), $httpRequest->getMethod())) {
448: return;
449: }
450:
451: if ($httpRequest->isMethod('post')) {
452: $data = Arrays::mergeTree($httpRequest->getPost(), $httpRequest->getFiles());
453: } else {
454: $data = $httpRequest->getQuery();
455: }
456:
457: if ($tracker = $this->getComponent(self::TRACKER_ID, FALSE)) {
458: if (!isset($data[self::TRACKER_ID]) || $data[self::TRACKER_ID] !== $tracker->getValue()) {
459: return;
460: }
461: }
462:
463: return $data;
464: }
465:
466:
467:
468:
469:
470:
471:
472: 473: 474: 475:
476: public function getValues()
477: {
478: $values = parent::getValues();
479: unset($values[self::TRACKER_ID], $values[self::PROTECTOR_ID]);
480: return $values;
481: }
482:
483:
484:
485:
486:
487:
488:
489: 490: 491: 492: 493:
494: public function addError($message)
495: {
496: $this->valid = FALSE;
497: if ($message !== NULL && !in_array($message, $this->errors, TRUE)) {
498: $this->errors[] = $message;
499: }
500: }
501:
502:
503:
504: 505: 506: 507:
508: public function getErrors()
509: {
510: return $this->errors;
511: }
512:
513:
514:
515: 516: 517:
518: public function hasErrors()
519: {
520: return (bool) $this->getErrors();
521: }
522:
523:
524:
525: 526: 527:
528: public function cleanErrors()
529: {
530: $this->errors = array();
531: $this->valid = NULL;
532: }
533:
534:
535:
536:
537:
538:
539:
540: 541: 542: 543:
544: public function getElementPrototype()
545: {
546: return $this->element;
547: }
548:
549:
550:
551: 552: 553: 554: 555:
556: public function setRenderer(IFormRenderer $renderer)
557: {
558: $this->renderer = $renderer;
559: return $this;
560: }
561:
562:
563:
564: 565: 566: 567:
568: final public function getRenderer()
569: {
570: if ($this->renderer === NULL) {
571: $this->renderer = new DefaultFormRenderer;
572: }
573: return $this->renderer;
574: }
575:
576:
577:
578: 579: 580: 581:
582: public function render()
583: {
584: $args = func_get_args();
585: array_unshift($args, $this);
586: echo call_user_func_array(array($this->getRenderer(), 'render'), $args);
587: }
588:
589:
590:
591: 592: 593: 594: 595:
596: public function __toString()
597: {
598: try {
599: return $this->getRenderer()->render($this);
600:
601: } catch (Exception $e) {
602: if (func_get_args() && func_get_arg(0)) {
603: throw $e;
604: } else {
605: Debugger::toStringException($e);
606: }
607: }
608: }
609:
610:
611:
612:
613:
614:
615:
616: 617: 618:
619: protected function getHttpRequest()
620: {
621: return Environment::getHttpRequest();
622: }
623:
624:
625:
626: 627: 628:
629: protected function getSession()
630: {
631: return Environment::getSession();
632: }
633:
634: }
635: