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