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: 35: 36: 37: 38: 39: 40: 41: 42:
43: class Form extends FormContainer
44: {
45:
46: const EQUAL = ':equal';
47: const IS_IN = ':equal';
48: const FILLED = ':filled';
49: const VALID = ':valid';
50:
51:
52: const SUBMITTED = ':submitted';
53:
54:
55: const MIN_LENGTH = ':minLength';
56: const MAX_LENGTH = ':maxLength';
57: const LENGTH = ':length';
58: const EMAIL = ':email';
59: const URL = ':url';
60: const REGEXP = ':regexp';
61: const INTEGER = ':integer';
62: const NUMERIC = ':integer';
63: const FLOAT = ':float';
64: const RANGE = ':range';
65:
66:
67: const MAX_FILE_SIZE = ':fileSize';
68: const MIME_TYPE = ':mimeType';
69:
70:
71: const SCRIPT = 'Nette\\Forms\\InstantClientScript::javascript';
72:
73:
74:
75: const GET = 'get';
76: const POST = 'post';
77:
78:
79:
80: const TRACKER_ID = '_form_';
81:
82:
83: const PROTECTOR_ID = '_token_';
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: private $encoding = 'UTF-8';
114:
115:
116:
117: 118: 119: 120:
121: public function __construct($name = NULL)
122: {
123: $this->element = Nette\Web\Html::el('form');
124: $this->element->action = '';
125: $this->element->method = self::POST;
126: $this->element->id = 'frm-' . $name;
127:
128: $this->monitor(__CLASS__);
129: if ($name !== NULL) {
130: $tracker = new HiddenField($name);
131: $tracker->unmonitor(__CLASS__);
132: $this[self::TRACKER_ID] = $tracker;
133: }
134: parent::__construct(NULL, $name);
135: }
136:
137:
138:
139: 140: 141: 142: 143: 144:
145: protected function attached($obj)
146: {
147: if ($obj instanceof self) {
148: throw new \InvalidStateException('Nested forms are forbidden.');
149: }
150: }
151:
152:
153:
154: 155: 156: 157:
158: final public function getForm($need = TRUE)
159: {
160: return $this;
161: }
162:
163:
164:
165: 166: 167: 168: 169:
170: public function setAction($url)
171: {
172: $this->element->action = $url;
173: return $this;
174: }
175:
176:
177:
178: 179: 180: 181:
182: public function getAction()
183: {
184: return $this->element->action;
185: }
186:
187:
188:
189: 190: 191: 192: 193:
194: public function setMethod($method)
195: {
196: if ($this->httpData !== NULL) {
197: throw new \InvalidStateException(__METHOD__ . '() must be called until the form is empty.');
198: }
199: $this->element->method = strtolower($method);
200: return $this;
201: }
202:
203:
204:
205: 206: 207: 208:
209: public function getMethod()
210: {
211: return $this->element->method;
212: }
213:
214:
215:
216: 217: 218:
219: public function addTracker()
220: {
221: throw new \DeprecatedException(__METHOD__ . '() is deprecated; pass form name to the constructor.');
222: }
223:
224:
225:
226: 227: 228: 229: 230: 231:
232: public function addProtection($message = NULL, $timeout = NULL)
233: {
234: $session = $this->getSession()->getNamespace('Nette.Forms.Form/CSRF');
235: $key = "key$timeout";
236: if (isset($session->$key)) {
237: $token = $session->$key;
238: } else {
239: $session->$key = $token = md5(uniqid('', TRUE));
240: }
241: $session->setExpiration($timeout, $key);
242: $this[self::PROTECTOR_ID] = new HiddenField($token);
243: $this[self::PROTECTOR_ID]->addRule(':equal', empty($message) ? 'Security token did not match. Possible CSRF attack.' : $message, $token);
244: }
245:
246:
247:
248: 249: 250: 251: 252: 253:
254: public function addGroup($caption = NULL, $setAsCurrent = TRUE)
255: {
256: $group = new FormGroup;
257: $group->setOption('label', $caption);
258: $group->setOption('visual', TRUE);
259:
260: if ($setAsCurrent) {
261: $this->setCurrentGroup($group);
262: }
263:
264: if (isset($this->groups[$caption])) {
265: return $this->groups[] = $group;
266: } else {
267: return $this->groups[$caption] = $group;
268: }
269: }
270:
271:
272:
273: 274: 275: 276: 277:
278: public function removeGroup($name)
279: {
280: if (is_string($name) && isset($this->groups[$name])) {
281: $group = $this->groups[$name];
282:
283: } elseif ($name instanceof FormGroup && in_array($name, $this->groups, TRUE)) {
284: $group = $name;
285: $name = array_search($group, $this->groups, TRUE);
286:
287: } else {
288: throw new \InvalidArgumentException("Group not found in form '$this->name'");
289: }
290:
291: foreach ($group->getControls() as $control) {
292: $this->removeComponent($control);
293: }
294:
295: unset($this->groups[$name]);
296: }
297:
298:
299:
300: 301: 302: 303:
304: public function getGroups()
305: {
306: return $this->groups;
307: }
308:
309:
310:
311: 312: 313: 314: 315:
316: public function getGroup($name)
317: {
318: return isset($this->groups[$name]) ? $this->groups[$name] : NULL;
319: }
320:
321:
322:
323: 324: 325: 326: 327:
328: public function setEncoding($value)
329: {
330: $this->encoding = empty($value) ? 'UTF-8' : strtoupper($value);
331: if ($this->encoding !== 'UTF-8' && !extension_loaded('mbstring')) {
332: throw new \Exception("The PHP extension 'mbstring' is required for this encoding but is not loaded.");
333: }
334: return $this;
335: }
336:
337:
338:
339: 340: 341: 342:
343: final public function getEncoding()
344: {
345: return $this->encoding;
346: }
347:
348:
349:
350:
351:
352:
353:
354: 355: 356: 357: 358:
359: public function setTranslator(Nette\ITranslator $translator = NULL)
360: {
361: $this->translator = $translator;
362: return $this;
363: }
364:
365:
366:
367: 368: 369: 370:
371: final public function getTranslator()
372: {
373: return $this->translator;
374: }
375:
376:
377:
378:
379:
380:
381:
382: 383: 384: 385:
386: public function isAnchored()
387: {
388: return TRUE;
389: }
390:
391:
392:
393: 394: 395: 396:
397: final public function isSubmitted()
398: {
399: if ($this->submittedBy === NULL) {
400: $this->getHttpData();
401: $this->submittedBy = !empty($this->httpData);
402: }
403: return $this->submittedBy;
404: }
405:
406:
407:
408: 409: 410: 411: 412:
413: public function setSubmittedBy(ISubmitterControl $by = NULL)
414: {
415: $this->submittedBy = $by === NULL ? FALSE : $by;
416: return $this;
417: }
418:
419:
420:
421: 422: 423: 424:
425: final public function getHttpData()
426: {
427: if ($this->httpData === NULL) {
428: if (!$this->isAnchored()) {
429: throw new \InvalidStateException('Form is not anchored and therefore can not determine whether it was submitted.');
430: }
431: $this->httpData = (array) $this->receiveHttpData();
432: }
433: return $this->httpData;
434: }
435:
436:
437:
438: 439: 440: 441:
442: public function fireEvents()
443: {
444: if (!$this->isSubmitted()) {
445: return;
446:
447: } elseif ($this->submittedBy instanceof ISubmitterControl) {
448: if (!$this->submittedBy->getValidationScope() || $this->isValid()) {
449: $this->submittedBy->click();
450: $this->onSubmit($this);
451: } else {
452: $this->submittedBy->onInvalidClick($this->submittedBy);
453: $this->onInvalidSubmit($this);
454: }
455:
456: } elseif ($this->isValid()) {
457: $this->onSubmit($this);
458:
459: } else {
460: $this->onInvalidSubmit($this);
461: }
462: }
463:
464:
465:
466: 467: 468: 469:
470: protected function receiveHttpData()
471: {
472: $httpRequest = $this->getHttpRequest();
473: if (strcasecmp($this->getMethod(), $httpRequest->getMethod())) {
474: return;
475: }
476:
477: $httpRequest->setEncoding($this->encoding);
478: if ($httpRequest->isMethod('post')) {
479: $data = Nette\ArrayTools::mergeTree($httpRequest->getPost(), $httpRequest->getFiles());
480: } else {
481: $data = $httpRequest->getQuery();
482: }
483:
484: if ($tracker = $this->getComponent(self::TRACKER_ID, FALSE)) {
485: if (!isset($data[self::TRACKER_ID]) || $data[self::TRACKER_ID] !== $tracker->getValue()) {
486: return;
487: }
488: }
489:
490: return $data;
491: }
492:
493:
494:
495: 496: 497:
498: public function processHttpRequest()
499: {
500: trigger_error(__METHOD__ . '() is deprecated; use fireEvents() instead.', E_USER_WARNING);
501: $this->fireEvents();
502: }
503:
504:
505:
506:
507:
508:
509:
510: 511: 512: 513:
514: public function getValues()
515: {
516: $values = parent::getValues();
517: unset($values[self::TRACKER_ID], $values[self::PROTECTOR_ID]);
518: return $values;
519: }
520:
521:
522:
523:
524:
525:
526:
527: 528: 529: 530: 531:
532: public function addError($message)
533: {
534: $this->valid = FALSE;
535: if ($message !== NULL && !in_array($message, $this->errors, TRUE)) {
536: $this->errors[] = $message;
537: }
538: }
539:
540:
541:
542: 543: 544: 545:
546: public function getErrors()
547: {
548: return $this->errors;
549: }
550:
551:
552:
553: 554: 555:
556: public function hasErrors()
557: {
558: return (bool) $this->getErrors();
559: }
560:
561:
562:
563: 564: 565:
566: public function cleanErrors()
567: {
568: $this->errors = array();
569: $this->valid = NULL;
570: }
571:
572:
573:
574:
575:
576:
577:
578: 579: 580: 581:
582: public function getElementPrototype()
583: {
584: return $this->element;
585: }
586:
587:
588:
589: 590: 591: 592: 593:
594: public function setRenderer(IFormRenderer $renderer)
595: {
596: $this->renderer = $renderer;
597: return $this;
598: }
599:
600:
601:
602: 603: 604: 605:
606: final public function getRenderer()
607: {
608: if ($this->renderer === NULL) {
609: $this->renderer = new ConventionalRenderer;
610: }
611: return $this->renderer;
612: }
613:
614:
615:
616: 617: 618: 619:
620: public function render()
621: {
622: $args = func_get_args();
623: array_unshift($args, $this);
624: $s = call_user_func_array(array($this->getRenderer(), 'render'), $args);
625:
626: if (strcmp($this->encoding, 'UTF-8')) {
627: echo mb_convert_encoding($s, 'HTML-ENTITIES', 'UTF-8');
628: } else {
629: echo $s;
630: }
631: }
632:
633:
634:
635: 636: 637: 638: 639:
640: public function __toString()
641: {
642: try {
643: if (strcmp($this->encoding, 'UTF-8')) {
644: return mb_convert_encoding($this->getRenderer()->render($this), 'HTML-ENTITIES', 'UTF-8');
645: } else {
646: return $this->getRenderer()->render($this);
647: }
648:
649: } catch (\Exception $e) {
650: if (func_get_args() && func_get_arg(0)) {
651: throw $e;
652: } else {
653: Nette\Debug::toStringException($e);
654: }
655: }
656: }
657:
658:
659:
660:
661:
662:
663:
664: 665: 666:
667: protected function getHttpRequest()
668: {
669: return class_exists('Nette\Environment') ? Nette\Environment::getHttpRequest() : new Nette\Web\HttpRequest;
670: }
671:
672:
673:
674: 675: 676:
677: protected function getSession()
678: {
679: return Nette\Environment::getSession();
680: }
681:
682: }
683: