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