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