1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette\Forms;
9:
10: use Nette;
11:
12:
13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29:
30: class Form extends Container
31: {
32:
33: const EQUAL = ':equal',
34: IS_IN = ':equal',
35: NOT_EQUAL = ':notEqual',
36: FILLED = ':filled',
37: BLANK = ':blank',
38: REQUIRED = self::FILLED,
39: VALID = ':valid';
40:
41:
42: const PROTECTION = 'Nette\Forms\Controls\HiddenField::validateEqual';
43:
44:
45: const SUBMITTED = ':submitted';
46:
47:
48: const MIN_LENGTH = ':minLength',
49: MAX_LENGTH = ':maxLength',
50: LENGTH = ':length',
51: EMAIL = ':email',
52: URL = ':url',
53: REGEXP = ':regexp',
54: PATTERN = ':pattern',
55: INTEGER = ':integer',
56: NUMERIC = ':integer',
57: FLOAT = ':float',
58: RANGE = ':range';
59:
60:
61: const COUNT = ':length';
62:
63:
64: const MAX_FILE_SIZE = ':fileSize',
65: MIME_TYPE = ':mimeType',
66: IMAGE = ':image';
67:
68:
69: const GET = 'get',
70: POST = 'post';
71:
72:
73: const TRACKER_ID = '_form_';
74:
75:
76: const PROTECTOR_ID = '_token_';
77:
78:
79: public $onSuccess;
80:
81:
82: public $onError;
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: public $httpRequest;
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: protected function attached($obj)
143: {
144: if ($obj instanceof self) {
145: throw new Nette\InvalidStateException('Nested forms are forbidden.');
146: }
147: }
148:
149:
150: 151: 152: 153:
154: public function getForm($need = TRUE)
155: {
156: return $this;
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: public function getAction()
177: {
178: return $this->element->action;
179: }
180:
181:
182: 183: 184: 185: 186:
187: public function setMethod($method)
188: {
189: if ($this->httpData !== NULL) {
190: throw new Nette\InvalidStateException(__METHOD__ . '() must be called until the form is empty.');
191: }
192: $this->element->method = strtolower($method);
193: return $this;
194: }
195:
196:
197: 198: 199: 200:
201: public function getMethod()
202: {
203: return $this->element->method;
204: }
205:
206:
207: 208: 209: 210: 211: 212:
213: public function addProtection($message = NULL, $timeout = NULL)
214: {
215: $session = $this->getSession()->getSection('Nette.Forms.Form/CSRF');
216: $key = "key$timeout";
217: if (isset($session->$key)) {
218: $token = $session->$key;
219: } else {
220: $session->$key = $token = Nette\Utils\Strings::random();
221: }
222: $session->setExpiration($timeout, $key);
223: $token = base64_encode($token ^ $this->getSession()->getId());
224: $this[self::PROTECTOR_ID] = new Controls\HiddenField($token);
225: $this[self::PROTECTOR_ID]->addRule(self::PROTECTION, $message, $token);
226: }
227:
228:
229: 230: 231: 232: 233: 234:
235: public function addGroup($caption = NULL, $setAsCurrent = TRUE)
236: {
237: $group = new ControlGroup;
238: $group->setOption('label', $caption);
239: $group->setOption('visual', TRUE);
240:
241: if ($setAsCurrent) {
242: $this->setCurrentGroup($group);
243: }
244:
245: if (isset($this->groups[$caption])) {
246: return $this->groups[] = $group;
247: } else {
248: return $this->groups[$caption] = $group;
249: }
250: }
251:
252:
253: 254: 255: 256: 257:
258: public function removeGroup($name)
259: {
260: if (is_string($name) && isset($this->groups[$name])) {
261: $group = $this->groups[$name];
262:
263: } elseif ($name instanceof ControlGroup && in_array($name, $this->groups, TRUE)) {
264: $group = $name;
265: $name = array_search($group, $this->groups, TRUE);
266:
267: } else {
268: throw new Nette\InvalidArgumentException("Group not found in form '$this->name'");
269: }
270:
271: foreach ($group->getControls() as $control) {
272: $control->getParent()->removeComponent($control);
273: }
274:
275: unset($this->groups[$name]);
276: }
277:
278:
279: 280: 281: 282:
283: public function getGroups()
284: {
285: return $this->groups;
286: }
287:
288:
289: 290: 291: 292: 293:
294: public function getGroup($name)
295: {
296: return isset($this->groups[$name]) ? $this->groups[$name] : NULL;
297: }
298:
299:
300:
301:
302:
303: 304: 305: 306:
307: public function setTranslator(Nette\Localization\ITranslator $translator = NULL)
308: {
309: $this->translator = $translator;
310: return $this;
311: }
312:
313:
314: 315: 316: 317:
318: public function getTranslator()
319: {
320: return $this->translator;
321: }
322:
323:
324:
325:
326:
327: 328: 329: 330:
331: public function isAnchored()
332: {
333: return TRUE;
334: }
335:
336:
337: 338: 339: 340:
341: public function isSubmitted()
342: {
343: if ($this->submittedBy === NULL) {
344: $this->getHttpData();
345: }
346: return $this->submittedBy;
347: }
348:
349:
350: 351: 352: 353:
354: public function isSuccess()
355: {
356: return $this->isSubmitted() && $this->isValid();
357: }
358:
359:
360: 361: 362: 363:
364: public function setSubmittedBy(ISubmitterControl $by = NULL)
365: {
366: $this->submittedBy = $by === NULL ? FALSE : $by;
367: return $this;
368: }
369:
370:
371: 372: 373: 374:
375: public function getHttpData()
376: {
377: if ($this->httpData === NULL) {
378: if (!$this->isAnchored()) {
379: throw new Nette\InvalidStateException('Form is not anchored and therefore can not determine whether it was submitted.');
380: }
381: $data = $this->receiveHttpData();
382: $this->httpData = (array) $data;
383: $this->submittedBy = is_array($data);
384: }
385: return $this->httpData;
386: }
387:
388:
389: 390: 391: 392:
393: public function fireEvents()
394: {
395: if (!$this->isSubmitted()) {
396: return;
397:
398: } elseif ($this->submittedBy instanceof ISubmitterControl) {
399: if (!$this->submittedBy->getValidationScope() || $this->isValid()) {
400: $this->submittedBy->click();
401: $valid = TRUE;
402: } else {
403: $this->submittedBy->onInvalidClick($this->submittedBy);
404: }
405: }
406:
407: if (isset($valid) || $this->isValid()) {
408: $this->onSuccess($this);
409: } else {
410: $this->onError($this);
411: if ($this->onInvalidSubmit) {
412: trigger_error(__CLASS__ . '->onInvalidSubmit is deprecated; use onError instead.', E_USER_WARNING);
413: $this->onInvalidSubmit($this);
414: }
415: }
416:
417: if ($this->onSuccess) {
418: $this->onSubmit($this);
419: } elseif ($this->onSubmit) {
420: trigger_error(__CLASS__ . '->onSubmit changed its behavior; use onSuccess instead.', E_USER_WARNING);
421: if (isset($valid) || $this->isValid()) {
422: $this->onSubmit($this);
423: }
424: }
425: }
426:
427:
428: 429: 430: 431:
432: protected function receiveHttpData()
433: {
434: $httpRequest = $this->getHttpRequest();
435: if (strcasecmp($this->getMethod(), $httpRequest->getMethod())) {
436: return;
437: }
438:
439: if ($httpRequest->isMethod('post')) {
440: $data = Nette\Utils\Arrays::mergeTree($httpRequest->getPost(), $httpRequest->getFiles());
441: } else {
442: $data = $httpRequest->getQuery();
443: if (!$data) {
444: return;
445: }
446: }
447:
448: if ($tracker = $this->getComponent(self::TRACKER_ID, FALSE)) {
449: if (!isset($data[self::TRACKER_ID]) || $data[self::TRACKER_ID] !== $tracker->getValue()) {
450: return;
451: }
452: }
453:
454: return $data;
455: }
456:
457:
458:
459:
460:
461: 462: 463: 464:
465: public function getValues($asArray = FALSE)
466: {
467: $values = parent::getValues($asArray);
468: unset($values[self::TRACKER_ID], $values[self::PROTECTOR_ID]);
469: return $values;
470: }
471:
472:
473:
474:
475:
476: 477: 478: 479: 480:
481: public function addError($message)
482: {
483: $this->valid = FALSE;
484: if ($message !== NULL && !in_array($message, $this->errors, TRUE)) {
485: $this->errors[] = $message;
486: }
487: }
488:
489:
490: 491: 492: 493:
494: public function getErrors()
495: {
496: return $this->errors;
497: }
498:
499:
500: 501: 502:
503: public function hasErrors()
504: {
505: return (bool) $this->getErrors();
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: public function getElementPrototype()
527: {
528: return $this->element;
529: }
530:
531:
532: 533: 534: 535:
536: public function setRenderer(IFormRenderer $renderer)
537: {
538: $this->renderer = $renderer;
539: return $this;
540: }
541:
542:
543: 544: 545: 546:
547: public function getRenderer()
548: {
549: if ($this->renderer === NULL) {
550: $this->renderer = new Rendering\DefaultFormRenderer;
551: }
552: return $this->renderer;
553: }
554:
555:
556: 557: 558: 559:
560: public function render()
561: {
562: $args = func_get_args();
563: array_unshift($args, $this);
564: echo call_user_func_array(array($this->getRenderer(), 'render'), $args);
565: }
566:
567:
568: 569: 570: 571: 572:
573: public function __toString()
574: {
575: try {
576: return $this->getRenderer()->render($this);
577:
578: } catch (\Exception $e) {
579: if (func_num_args()) {
580: throw $e;
581: }
582: trigger_error("Exception in " . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
583: }
584: }
585:
586:
587:
588:
589:
590: 591: 592:
593: protected function getHttpRequest()
594: {
595: if (!$this->httpRequest) {
596: $factory = new Nette\Http\RequestFactory;
597: $this->httpRequest = $factory->setEncoding('UTF-8')->createHttpRequest();
598: }
599: return $this->httpRequest;
600: }
601:
602:
603: 604: 605:
606: protected function getSession()
607: {
608: if (!$this->httpRequest) {
609: $this->httpRequest = Nette\Environment::getHttpRequest();
610: }
611: return Nette\Environment::getSession();
612: }
613:
614: }
615: