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