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