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: class Form extends Container implements Nette\Utils\IHtmlString
20: {
21:
22: const EQUAL = ':equal',
23: IS_IN = self::EQUAL,
24: NOT_EQUAL = ':notEqual',
25: IS_NOT_IN = self::NOT_EQUAL,
26: FILLED = ':filled',
27: BLANK = ':blank',
28: REQUIRED = self::FILLED,
29: VALID = ':valid';
30:
31:
32: const PROTECTION = Controls\CsrfProtection::PROTECTION;
33:
34:
35: const SUBMITTED = ':submitted';
36:
37:
38: const MIN_LENGTH = ':minLength',
39: MAX_LENGTH = ':maxLength',
40: LENGTH = ':length',
41: EMAIL = ':email',
42: URL = ':url',
43: PATTERN = ':pattern',
44: INTEGER = ':integer',
45: NUMERIC = ':integer',
46: FLOAT = ':float',
47: MIN = ':min',
48: MAX = ':max',
49: RANGE = ':range';
50:
51:
52: const COUNT = self::LENGTH;
53:
54:
55: const MAX_FILE_SIZE = ':fileSize',
56: MIME_TYPE = ':mimeType',
57: IMAGE = ':image',
58: MAX_POST_SIZE = ':maxPostSize';
59:
60:
61: const GET = 'get',
62: POST = 'post';
63:
64:
65: const DATA_TEXT = 1;
66: const DATA_LINE = 2;
67: const DATA_FILE = 3;
68: const DATA_KEYS = 8;
69:
70:
71: const TRACKER_ID = '_form_';
72:
73:
74: const PROTECTOR_ID = '_token_';
75:
76:
77: public $onSuccess;
78:
79:
80: public $onError;
81:
82:
83: public $onSubmit;
84:
85:
86: private $submittedBy;
87:
88:
89: private $httpData;
90:
91:
92: private $element;
93:
94:
95: private $renderer;
96:
97:
98: private $translator;
99:
100:
101: private $groups = array();
102:
103:
104: private $errors = array();
105:
106:
107: public $httpRequest;
108:
109:
110: 111: 112: 113:
114: public function __construct($name = NULL)
115: {
116: parent::__construct();
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: $this->setParent(NULL, $name);
123: }
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:
355: public function setSubmittedBy(ISubmitterControl $by = NULL)
356: {
357: $this->submittedBy = $by === NULL ? FALSE : $by;
358: return $this;
359: }
360:
361:
362: 363: 364: 365:
366: public function getHttpData($type = NULL, $htmlName = NULL)
367: {
368: if ($this->httpData === NULL) {
369: if (!$this->isAnchored()) {
370: throw new Nette\InvalidStateException('Form is not anchored and therefore can not determine whether it was submitted.');
371: }
372: $data = $this->receiveHttpData();
373: $this->httpData = (array) $data;
374: $this->submittedBy = is_array($data);
375: }
376: if ($htmlName === NULL) {
377: return $this->httpData;
378: }
379: return Helpers::extractHttpData($this->httpData, $htmlName, $type);
380: }
381:
382:
383: 384: 385: 386:
387: public function fireEvents()
388: {
389: if (!$this->isSubmitted()) {
390: return;
391:
392: } elseif (!$this->getErrors()) {
393: $this->validate();
394: }
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->isValid()) {
405: $this->onError($this);
406:
407: } elseif ($this->onSuccess !== NULL) {
408: if (!is_array($this->onSuccess) && !$this->onSuccess instanceof \Traversable) {
409: throw new Nette\UnexpectedValueException('Property Form::$onSuccess must be array or Traversable, ' . gettype($this->onSuccess) . ' given.');
410: }
411: foreach ($this->onSuccess as $handler) {
412: $params = Nette\Utils\Callback::toReflection($handler)->getParameters();
413: $values = isset($params[1]) ? $this->getValues($params[1]->isArray()) : NULL;
414: Nette\Utils\Callback::invoke($handler, $this, $values);
415: if (!$this->isValid()) {
416: $this->onError($this);
417: break;
418: }
419: }
420: }
421:
422: $this->onSubmit($this);
423: }
424:
425:
426: 427: 428: 429:
430: protected function receiveHttpData()
431: {
432: $httpRequest = $this->getHttpRequest();
433: if (strcasecmp($this->getMethod(), $httpRequest->getMethod())) {
434: return;
435: }
436:
437: if ($httpRequest->isMethod('post')) {
438: $data = Nette\Utils\Arrays::mergeTree($httpRequest->getPost(), $httpRequest->getFiles());
439: } else {
440: $data = $httpRequest->getQuery();
441: if (!$data) {
442: return;
443: }
444: }
445:
446: if ($tracker = $this->getComponent(self::TRACKER_ID, FALSE)) {
447: if (!isset($data[self::TRACKER_ID]) || $data[self::TRACKER_ID] !== $tracker->getValue()) {
448: return;
449: }
450: }
451:
452: return $data;
453: }
454:
455:
456:
457:
458:
459: public function validate(array $controls = NULL)
460: {
461: $this->cleanErrors();
462: if ($controls === NULL && $this->submittedBy instanceof ISubmitterControl) {
463: $controls = $this->submittedBy->getValidationScope();
464: }
465: $this->validateMaxPostSize();
466: parent::validate($controls);
467: }
468:
469:
470:
471: public function validateMaxPostSize()
472: {
473: if (!$this->submittedBy || strcasecmp($this->getMethod(), 'POST') || empty($_SERVER['CONTENT_LENGTH'])) {
474: return;
475: }
476: $maxSize = ini_get('post_max_size');
477: $units = array('k' => 10, 'm' => 20, 'g' => 30);
478: if (isset($units[$ch = strtolower(substr($maxSize, -1))])) {
479: $maxSize = (int) $maxSize << $units[$ch];
480: }
481: if ($maxSize > 0 && $maxSize < $_SERVER['CONTENT_LENGTH']) {
482: $this->addError(sprintf(Validator::$messages[self::MAX_FILE_SIZE], $maxSize));
483: }
484: }
485:
486:
487: 488: 489: 490: 491:
492: public function addError($message)
493: {
494: $this->errors[] = $message;
495: }
496:
497:
498: 499: 500: 501:
502: public function getErrors()
503: {
504: return array_unique(array_merge($this->errors, parent::getErrors()));
505: }
506:
507:
508: 509: 510:
511: public function hasErrors()
512: {
513: return (bool) $this->getErrors();
514: }
515:
516:
517: 518: 519:
520: public function cleanErrors()
521: {
522: $this->errors = array();
523: }
524:
525:
526: 527: 528: 529:
530: public function getOwnErrors()
531: {
532: return array_unique($this->errors);
533: }
534:
535:
536:
537:
538:
539: 540: 541: 542:
543: public function getElementPrototype()
544: {
545: if (!$this->element) {
546: $this->element = Nette\Utils\Html::el('form');
547: $this->element->action = '';
548: $this->element->method = self::POST;
549: }
550: return $this->element;
551: }
552:
553:
554: 555: 556: 557:
558: public function setRenderer(IFormRenderer $renderer = NULL)
559: {
560: $this->renderer = $renderer;
561: return $this;
562: }
563:
564:
565: 566: 567: 568:
569: public function getRenderer()
570: {
571: if ($this->renderer === NULL) {
572: $this->renderer = new Rendering\DefaultFormRenderer;
573: }
574: return $this->renderer;
575: }
576:
577:
578: 579: 580: 581:
582: public function render()
583: {
584: $args = func_get_args();
585: array_unshift($args, $this);
586: echo call_user_func_array(array($this->getRenderer(), 'render'), $args);
587: }
588:
589:
590: 591: 592: 593: 594:
595: public function __toString()
596: {
597: try {
598: return $this->getRenderer()->render($this);
599:
600: } catch (\Throwable $e) {
601: } catch (\Exception $e) {
602: }
603: if (isset($e)) {
604: if (func_num_args()) {
605: throw $e;
606: }
607: trigger_error("Exception in " . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
608: }
609: }
610:
611:
612:
613:
614:
615: 616: 617:
618: private function getHttpRequest()
619: {
620: if (!$this->httpRequest) {
621: $factory = new Nette\Http\RequestFactory;
622: $this->httpRequest = $factory->createHttpRequest();
623: }
624: return $this->httpRequest;
625: }
626:
627:
628: 629: 630:
631: public function getToggles()
632: {
633: $toggles = array();
634: foreach ($this->getComponents(TRUE, 'Nette\Forms\Controls\BaseControl') as $control) {
635: $toggles = $control->getRules()->getToggleStates($toggles);
636: }
637: return $toggles;
638: }
639:
640: }
641: