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 = $name === NULL ? NULL : '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: public function setTranslator(Nette\Localization\ITranslator $translator = NULL)
320: {
321: $this->translator = $translator;
322: return $this;
323: }
324:
325:
326:
327: 328: 329: 330:
331: final public function getTranslator()
332: {
333: return $this->translator;
334: }
335:
336:
337:
338:
339:
340:
341:
342: 343: 344: 345:
346: public function isAnchored()
347: {
348: return TRUE;
349: }
350:
351:
352:
353: 354: 355: 356:
357: final public function isSubmitted()
358: {
359: if ($this->submittedBy === NULL && count($this->getControls())) {
360: $this->getHttpData();
361: $this->submittedBy = $this->httpData !== NULL;
362: }
363: return $this->submittedBy;
364: }
365:
366:
367:
368: 369: 370: 371:
372: final public function isSuccess()
373: {
374: return $this->isSubmitted() && $this->isValid();
375: }
376:
377:
378:
379: 380: 381: 382:
383: public function setSubmittedBy(ISubmitterControl $by = NULL)
384: {
385: $this->submittedBy = $by === NULL ? FALSE : $by;
386: return $this;
387: }
388:
389:
390:
391: 392: 393: 394:
395: final public function getHttpData()
396: {
397: if ($this->httpData === NULL) {
398: if (!$this->isAnchored()) {
399: throw new Nette\InvalidStateException('Form is not anchored and therefore can not determine whether it was submitted.');
400: }
401: $this->httpData = $this->receiveHttpData();
402: }
403: return $this->httpData;
404: }
405:
406:
407:
408: 409: 410: 411:
412: public function fireEvents()
413: {
414: if (!$this->isSubmitted()) {
415: return;
416:
417: } elseif ($this->submittedBy instanceof ISubmitterControl) {
418: if (!$this->submittedBy->getValidationScope() || $this->isValid()) {
419: $this->submittedBy->click();
420: $valid = TRUE;
421: } else {
422: $this->submittedBy->onInvalidClick($this->submittedBy);
423: }
424: }
425:
426: if (isset($valid) || $this->isValid()) {
427: $this->onSuccess($this);
428: } else {
429: $this->onError($this);
430: if ($this->onInvalidSubmit) {
431: trigger_error(__CLASS__ . '->onInvalidSubmit is deprecated; use onError instead.', E_USER_WARNING);
432: $this->onInvalidSubmit($this);
433: }
434: }
435:
436: if ($this->onSuccess) {
437: $this->onSubmit($this);
438: } elseif ($this->onSubmit) {
439: trigger_error(__CLASS__ . '->onSubmit changed its behavior; use onSuccess instead.', E_USER_WARNING);
440: if (isset($valid) || $this->isValid()) {
441: $this->onSubmit($this);
442: }
443: }
444: }
445:
446:
447:
448: 449: 450: 451:
452: protected function receiveHttpData()
453: {
454: $httpRequest = $this->getHttpRequest();
455: if (strcasecmp($this->getMethod(), $httpRequest->getMethod())) {
456: return;
457: }
458:
459: if ($httpRequest->isMethod('post')) {
460: $data = Nette\Utils\Arrays::mergeTree($httpRequest->getPost(), $httpRequest->getFiles());
461: } else {
462: $data = $httpRequest->getQuery();
463: }
464:
465: if ($tracker = $this->getComponent(self::TRACKER_ID, FALSE)) {
466: if (!isset($data[self::TRACKER_ID]) || $data[self::TRACKER_ID] !== $tracker->getValue()) {
467: return;
468: }
469: }
470:
471: return $data;
472: }
473:
474:
475:
476:
477:
478:
479:
480: 481: 482: 483:
484: public function getValues($asArray = FALSE)
485: {
486: $values = parent::getValues($asArray);
487: unset($values[self::TRACKER_ID], $values[self::PROTECTOR_ID]);
488: return $values;
489: }
490:
491:
492:
493:
494:
495:
496:
497: 498: 499: 500: 501:
502: public function addError($message)
503: {
504: $this->valid = FALSE;
505: if ($message !== NULL && !in_array($message, $this->errors, TRUE)) {
506: $this->errors[] = $message;
507: }
508: }
509:
510:
511:
512: 513: 514: 515:
516: public function getErrors()
517: {
518: return $this->errors;
519: }
520:
521:
522:
523: 524: 525:
526: public function hasErrors()
527: {
528: return (bool) $this->getErrors();
529: }
530:
531:
532:
533: 534: 535:
536: public function cleanErrors()
537: {
538: $this->errors = array();
539: $this->valid = NULL;
540: }
541:
542:
543:
544:
545:
546:
547:
548: 549: 550: 551:
552: public function getElementPrototype()
553: {
554: return $this->element;
555: }
556:
557:
558:
559: 560: 561: 562:
563: public function setRenderer(IFormRenderer $renderer)
564: {
565: $this->renderer = $renderer;
566: return $this;
567: }
568:
569:
570:
571: 572: 573: 574:
575: final public function getRenderer()
576: {
577: if ($this->renderer === NULL) {
578: $this->renderer = new Rendering\DefaultFormRenderer;
579: }
580: return $this->renderer;
581: }
582:
583:
584:
585: 586: 587: 588:
589: public function render()
590: {
591: $args = func_get_args();
592: array_unshift($args, $this);
593: echo call_user_func_array(array($this->getRenderer(), 'render'), $args);
594: }
595:
596:
597:
598: 599: 600: 601: 602:
603: public function __toString()
604: {
605: try {
606: return $this->getRenderer()->render($this);
607:
608: } catch (\Exception $e) {
609: if (func_get_args() && func_get_arg(0)) {
610: throw $e;
611: } else {
612: trigger_error("Exception in " . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
613: }
614: }
615: }
616:
617:
618:
619:
620:
621:
622:
623: 624: 625:
626: protected function getHttpRequest()
627: {
628: return Nette\Environment::getHttpRequest();
629: }
630:
631:
632:
633: 634: 635:
636: protected function getSession()
637: {
638: return Nette\Environment::getSession();
639: }
640:
641: }
642: