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