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