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