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