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