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