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