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