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