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