1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette\Forms;
9:
10: use Nette;
11:
12:
13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29:
30: class Form extends Container
31: {
32:
33: const EQUAL = ':equal',
34: IS_IN = self::EQUAL,
35: NOT_EQUAL = ':notEqual',
36: FILLED = ':filled',
37: BLANK = ':blank',
38: REQUIRED = self::FILLED,
39: VALID = ':valid';
40:
41:
42: const PROTECTION = Controls\CsrfProtection::PROTECTION;
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 = self::LENGTH;
62:
63:
64: const MAX_FILE_SIZE = ':fileSize',
65: MIME_TYPE = ':mimeType',
66: IMAGE = ':image',
67: MAX_POST_SIZE = ':maxPostSize';
68:
69:
70: const GET = 'get',
71: POST = 'post';
72:
73:
74: const DATA_TEXT = 1;
75: const DATA_LINE = 2;
76: const DATA_FILE = 3;
77: const DATA_KEYS = 8;
78:
79:
80: const TRACKER_ID = '_form_';
81:
82:
83: const PROTECTOR_ID = '_token_';
84:
85:
86: public $onSuccess;
87:
88:
89: public $onError;
90:
91:
92: public $onSubmit;
93:
94:
95: private $submittedBy;
96:
97:
98: private $httpData;
99:
100:
101: private $element;
102:
103:
104: private $renderer;
105:
106:
107: private $translator;
108:
109:
110: private $groups = array();
111:
112:
113: private $errors = array();
114:
115:
116: public $httpRequest;
117:
118:
119: 120: 121: 122:
123: public function __construct($name = NULL)
124: {
125: $this->element = Nette\Utils\Html::el('form');
126: $this->element->action = '';
127: $this->element->method = self::POST;
128: $this->element->id = $name === NULL ? NULL : 'frm-' . $name;
129:
130: $this->monitor(__CLASS__);
131: if ($name !== NULL) {
132: $tracker = new Controls\HiddenField($name);
133: $tracker->setOmitted();
134: $this[self::TRACKER_ID] = $tracker;
135: }
136: parent::__construct(NULL, $name);
137: }
138:
139:
140: 141: 142: 143: 144: 145:
146: protected function attached($obj)
147: {
148: if ($obj instanceof self) {
149: throw new Nette\InvalidStateException('Nested forms are forbidden.');
150: }
151: }
152:
153:
154: 155: 156: 157:
158: public function getForm($need = TRUE)
159: {
160: return $this;
161: }
162:
163:
164: 165: 166: 167: 168:
169: public function setAction($url)
170: {
171: $this->element->action = $url;
172: return $this;
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: public function setMethod($method)
192: {
193: if ($this->httpData !== NULL) {
194: throw new Nette\InvalidStateException(__METHOD__ . '() must be called until the form is empty.');
195: }
196: $this->element->method = strtolower($method);
197: return $this;
198: }
199:
200:
201: 202: 203: 204:
205: public function getMethod()
206: {
207: return $this->element->method;
208: }
209:
210:
211: 212: 213: 214: 215:
216: public function addProtection($message = NULL)
217: {
218: return $this[self::PROTECTOR_ID] = new Controls\CsrfProtection($message);
219: }
220:
221:
222: 223: 224: 225: 226: 227:
228: public function addGroup($caption = NULL, $setAsCurrent = TRUE)
229: {
230: $group = new ControlGroup;
231: $group->setOption('label', $caption);
232: $group->setOption('visual', TRUE);
233:
234: if ($setAsCurrent) {
235: $this->setCurrentGroup($group);
236: }
237:
238: if (!is_scalar($caption) || isset($this->groups[$caption])) {
239: return $this->groups[] = $group;
240: } else {
241: return $this->groups[$caption] = $group;
242: }
243: }
244:
245:
246: 247: 248: 249: 250:
251: public function removeGroup($name)
252: {
253: if (is_string($name) && isset($this->groups[$name])) {
254: $group = $this->groups[$name];
255:
256: } elseif ($name instanceof ControlGroup && in_array($name, $this->groups, TRUE)) {
257: $group = $name;
258: $name = array_search($group, $this->groups, TRUE);
259:
260: } else {
261: throw new Nette\InvalidArgumentException("Group not found in form '$this->name'");
262: }
263:
264: foreach ($group->getControls() as $control) {
265: $control->getParent()->removeComponent($control);
266: }
267:
268: unset($this->groups[$name]);
269: }
270:
271:
272: 273: 274: 275:
276: public function getGroups()
277: {
278: return $this->groups;
279: }
280:
281:
282: 283: 284: 285: 286:
287: public function getGroup($name)
288: {
289: return isset($this->groups[$name]) ? $this->groups[$name] : NULL;
290: }
291:
292:
293:
294:
295:
296: 297: 298: 299:
300: public function setTranslator(Nette\Localization\ITranslator $translator = NULL)
301: {
302: $this->translator = $translator;
303: return $this;
304: }
305:
306:
307: 308: 309: 310:
311: public function getTranslator()
312: {
313: return $this->translator;
314: }
315:
316:
317:
318:
319:
320: 321: 322: 323:
324: public function isAnchored()
325: {
326: return TRUE;
327: }
328:
329:
330: 331: 332: 333:
334: public function isSubmitted()
335: {
336: if ($this->submittedBy === NULL) {
337: $this->getHttpData();
338: }
339: return $this->submittedBy;
340: }
341:
342:
343: 344: 345: 346:
347: public function isSuccess()
348: {
349: return $this->isSubmitted() && $this->isValid();
350: }
351:
352:
353: 354: 355: 356:
357: public function setSubmittedBy(ISubmitterControl $by = NULL)
358: {
359: $this->submittedBy = $by === NULL ? FALSE : $by;
360: return $this;
361: }
362:
363:
364: 365: 366: 367:
368: public function getHttpData($type = NULL, $htmlName = NULL)
369: {
370: if ($this->httpData === NULL) {
371: if (!$this->isAnchored()) {
372: throw new Nette\InvalidStateException('Form is not anchored and therefore can not determine whether it was submitted.');
373: }
374: $data = $this->receiveHttpData();
375: $this->httpData = (array) $data;
376: $this->submittedBy = is_array($data);
377: }
378: if ($htmlName === NULL) {
379: return $this->httpData;
380: }
381: return Helpers::extractHttpData($this->httpData, $htmlName, $type);
382: }
383:
384:
385: 386: 387: 388:
389: public function fireEvents()
390: {
391: if (!$this->isSubmitted()) {
392: return;
393:
394: } elseif (!$this->getErrors()) {
395: $this->validate();
396: }
397:
398: if ($this->submittedBy instanceof ISubmitterControl) {
399: if ($this->isValid()) {
400: $this->submittedBy->onClick($this->submittedBy);
401: } else {
402: $this->submittedBy->onInvalidClick($this->submittedBy);
403: }
404: }
405:
406: if ($this->onSuccess) {
407: foreach ($this->onSuccess as $handler) {
408: if (!$this->isValid()) {
409: $this->onError($this);
410: break;
411: }
412: Nette\Utils\Callback::invoke($handler, $this);
413: }
414: } elseif (!$this->isValid()) {
415: $this->onError($this);
416: }
417: $this->onSubmit($this);
418: }
419:
420:
421: 422: 423: 424:
425: protected function receiveHttpData()
426: {
427: $httpRequest = $this->getHttpRequest();
428: if (strcasecmp($this->getMethod(), $httpRequest->getMethod())) {
429: return;
430: }
431:
432: if ($httpRequest->isMethod('post')) {
433: $data = Nette\Utils\Arrays::mergeTree($httpRequest->getPost(), $httpRequest->getFiles());
434: } else {
435: $data = $httpRequest->getQuery();
436: if (!$data) {
437: return;
438: }
439: }
440:
441: if ($tracker = $this->getComponent(self::TRACKER_ID, FALSE)) {
442: if (!isset($data[self::TRACKER_ID]) || $data[self::TRACKER_ID] !== $tracker->getValue()) {
443: return;
444: }
445: }
446:
447: return $data;
448: }
449:
450:
451:
452:
453:
454: public function validate(array $controls = NULL)
455: {
456: $this->cleanErrors();
457: if ($controls === NULL && $this->submittedBy instanceof ISubmitterControl) {
458: $controls = $this->submittedBy->getValidationScope();
459: }
460: $this->validateMaxPostSize();
461: parent::validate($controls);
462: }
463:
464:
465:
466: public function validateMaxPostSize()
467: {
468: if (!$this->submittedBy || strcasecmp($this->getMethod(), 'POST') || empty($_SERVER['CONTENT_LENGTH'])) {
469: return;
470: }
471: $maxSize = ini_get('post_max_size');
472: $units = array('k' => 10, 'm' => 20, 'g' => 30);
473: if (isset($units[$ch = strtolower(substr($maxSize, -1))])) {
474: $maxSize <<= $units[$ch];
475: }
476: if ($maxSize > 0 && $maxSize < $_SERVER['CONTENT_LENGTH']) {
477: $this->addError(sprintf(Rules::$defaultMessages[self::MAX_FILE_SIZE], $maxSize));
478: }
479: }
480:
481:
482: 483: 484: 485: 486:
487: public function addError($message)
488: {
489: $this->errors[] = $message;
490: }
491:
492:
493: 494: 495: 496:
497: public function getErrors()
498: {
499: return array_unique(array_merge($this->errors, parent::getErrors()));
500: }
501:
502:
503: 504: 505:
506: public function hasErrors()
507: {
508: return (bool) $this->getErrors();
509: }
510:
511:
512: 513: 514:
515: public function cleanErrors()
516: {
517: $this->errors = array();
518: }
519:
520:
521: 522: 523: 524:
525: public function getOwnErrors()
526: {
527: return array_unique($this->errors);
528: }
529:
530:
531:
532: public function getAllErrors()
533: {
534: trigger_error(__METHOD__ . '() is deprecated; use getErrors() instead.', E_USER_DEPRECATED);
535: return $this->getErrors();
536: }
537:
538:
539:
540:
541:
542: 543: 544: 545:
546: public function getElementPrototype()
547: {
548: return $this->element;
549: }
550:
551:
552: 553: 554: 555:
556: public function setRenderer(IFormRenderer $renderer = NULL)
557: {
558: $this->renderer = $renderer;
559: return $this;
560: }
561:
562:
563: 564: 565: 566:
567: public function getRenderer()
568: {
569: if ($this->renderer === NULL) {
570: $this->renderer = new Rendering\DefaultFormRenderer;
571: }
572: return $this->renderer;
573: }
574:
575:
576: 577: 578: 579:
580: public function render()
581: {
582: $args = func_get_args();
583: array_unshift($args, $this);
584: echo call_user_func_array(array($this->getRenderer(), 'render'), $args);
585: }
586:
587:
588: 589: 590: 591: 592:
593: public function __toString()
594: {
595: try {
596: return $this->getRenderer()->render($this);
597:
598: } catch (\Throwable $e) {
599: } catch (\Exception $e) {
600: }
601: if (isset($e)) {
602: if (func_num_args()) {
603: throw $e;
604: }
605: trigger_error("Exception in " . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
606: }
607: }
608:
609:
610:
611:
612:
613: 614: 615:
616: private function getHttpRequest()
617: {
618: if (!$this->httpRequest) {
619: $factory = new Nette\Http\RequestFactory;
620: $this->httpRequest = $factory->createHttpRequest();
621: }
622: return $this->httpRequest;
623: }
624:
625:
626: 627: 628:
629: public function getToggles()
630: {
631: $toggles = array();
632: foreach ($this->getControls() as $control) {
633: $toggles = $control->getRules()->getToggleStates($toggles);
634: }
635: return $toggles;
636: }
637:
638: }
639: