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