Namespaces

  • Latte
    • Loaders
    • Macros
    • Runtime
  • Nette
    • Application
      • Responses
      • Routers
      • UI
    • Bridges
      • ApplicationLatte
      • ApplicationTracy
      • CacheLatte
      • DatabaseDI
      • DatabaseTracy
      • DITracy
      • FormsLatte
      • Framework
      • HttpTracy
      • SecurityTracy
    • Caching
      • Storages
    • ComponentModel
    • Database
      • Drivers
      • Reflection
      • Table
    • DI
      • Config
        • Adapters
      • Extensions
    • Diagnostics
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Latte
    • Loaders
    • Localization
    • Mail
    • Neon
    • PhpGenerator
    • Reflection
    • Security
    • Templating
    • Utils
  • NetteModule
  • none
  • Tracy

Classes

  • Container
  • ControlGroup
  • Form
  • Helpers
  • Rule
  • Rules

Interfaces

  • IControl
  • IFormRenderer
  • ISubmitterControl
  • Overview
  • Namespace
  • Class
  • Tree
  • Deprecated
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Nette Framework (http://nette.org)
  5:  * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
  6:  */
  7: 
  8: namespace Nette\Forms;
  9: 
 10: use Nette;
 11: 
 12: 
 13: /**
 14:  * Creates, validates and renders HTML forms.
 15:  *
 16:  * @author     David Grudl
 17:  *
 18:  * @property   mixed $action
 19:  * @property   string $method
 20:  * @property-read array $groups
 21:  * @property   Nette\Localization\ITranslator|NULL $translator
 22:  * @property-read bool $anchored
 23:  * @property-read ISubmitterControl|FALSE $submitted
 24:  * @property-read bool $success
 25:  * @property-read array $httpData
 26:  * @property-read array $errors
 27:  * @property-read Nette\Utils\Html $elementPrototype
 28:  * @property   IFormRenderer $renderer
 29:  */
 30: class Form extends Container implements Nette\Utils\IHtmlString
 31: {
 32:     /** validator */
 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:     /** @deprecated CSRF protection */
 42:     const PROTECTION = Controls\CsrfProtection::PROTECTION;
 43: 
 44:     // button
 45:     const SUBMITTED = ':submitted';
 46: 
 47:     // text
 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:     // multiselect
 62:     const COUNT = self::LENGTH;
 63: 
 64:     // file upload
 65:     const MAX_FILE_SIZE = ':fileSize',
 66:         MIME_TYPE = ':mimeType',
 67:         IMAGE = ':image',
 68:         MAX_POST_SIZE = ':maxPostSize';
 69: 
 70:     /** method */
 71:     const GET = 'get',
 72:         POST = 'post';
 73: 
 74:     /** submitted data types */
 75:     const DATA_TEXT = 1;
 76:     const DATA_LINE = 2;
 77:     const DATA_FILE = 3;
 78:     const DATA_KEYS = 8;
 79: 
 80:     /** @internal tracker ID */
 81:     const TRACKER_ID = '_form_';
 82: 
 83:     /** @internal protection token ID */
 84:     const PROTECTOR_ID = '_token_';
 85: 
 86:     /** @var array of function(Form $sender); Occurs when the form is submitted and successfully validated */
 87:     public $onSuccess;
 88: 
 89:     /** @var array of function(Form $sender); Occurs when the form is submitted and is not valid */
 90:     public $onError;
 91: 
 92:     /** @var array of function(Form $sender); Occurs when the form is submitted */
 93:     public $onSubmit;
 94: 
 95:     /** @var mixed or NULL meaning: not detected yet */
 96:     private $submittedBy;
 97: 
 98:     /** @var array */
 99:     private $httpData;
100: 
101:     /** @var Nette\Utils\Html  <form> element */
102:     private $element;
103: 
104:     /** @var IFormRenderer */
105:     private $renderer;
106: 
107:     /** @var Nette\Localization\ITranslator */
108:     private $translator;
109: 
110:     /** @var ControlGroup[] */
111:     private $groups = array();
112: 
113:     /** @var array */
114:     private $errors = array();
115: 
116:     /** @var Nette\Http\IRequest  used only by standalone form */
117:     public $httpRequest;
118: 
119: 
120:     /**
121:      * Form constructor.
122:      * @param  string
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:      * @return void
138:      */
139:     protected function validateParent(Nette\ComponentModel\IContainer $parent)
140:     {
141:         parent::validateParent($parent);
142:         $this->monitor(__CLASS__);
143:     }
144: 
145: 
146:     /**
147:      * This method will be called when the component (or component's parent)
148:      * becomes attached to a monitored object. Do not call this method yourself.
149:      * @param  Nette\ComponentModel\IComponent
150:      * @return void
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:      * Returns self.
162:      * @return Form
163:      */
164:     public function getForm($need = TRUE)
165:     {
166:         return $this;
167:     }
168: 
169: 
170:     /**
171:      * Sets form's action.
172:      * @param  mixed URI
173:      * @return self
174:      */
175:     public function setAction($url)
176:     {
177:         $this->getElementPrototype()->action = $url;
178:         return $this;
179:     }
180: 
181: 
182:     /**
183:      * Returns form's action.
184:      * @return mixed URI
185:      */
186:     public function getAction()
187:     {
188:         return $this->getElementPrototype()->action;
189:     }
190: 
191: 
192:     /**
193:      * Sets form's method.
194:      * @param  string get | post
195:      * @return self
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:      * Returns form's method.
209:      * @return string get | post
210:      */
211:     public function getMethod()
212:     {
213:         return $this->getElementPrototype()->method;
214:     }
215: 
216: 
217:     /**
218:      * Cross-Site Request Forgery (CSRF) form protection.
219:      * @param  string
220:      * @return Controls\CsrfProtection
221:      */
222:     public function addProtection($message = NULL)
223:     {
224:         return $this[self::PROTECTOR_ID] = new Controls\CsrfProtection($message);
225:     }
226: 
227: 
228:     /**
229:      * Adds fieldset group to the form.
230:      * @param  string  caption
231:      * @param  bool    set this group as current
232:      * @return ControlGroup
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:      * Removes fieldset group from form.
254:      * @param  string|ControlGroup
255:      * @return void
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:      * Returns all defined groups.
280:      * @return ControlGroup[]
281:      */
282:     public function getGroups()
283:     {
284:         return $this->groups;
285:     }
286: 
287: 
288:     /**
289:      * Returns the specified group.
290:      * @param  string  name
291:      * @return ControlGroup
292:      */
293:     public function getGroup($name)
294:     {
295:         return isset($this->groups[$name]) ? $this->groups[$name] : NULL;
296:     }
297: 
298: 
299:     /********************* translator ****************d*g**/
300: 
301: 
302:     /**
303:      * Sets translate adapter.
304:      * @return self
305:      */
306:     public function setTranslator(Nette\Localization\ITranslator $translator = NULL)
307:     {
308:         $this->translator = $translator;
309:         return $this;
310:     }
311: 
312: 
313:     /**
314:      * Returns translate adapter.
315:      * @return Nette\Localization\ITranslator|NULL
316:      */
317:     public function getTranslator()
318:     {
319:         return $this->translator;
320:     }
321: 
322: 
323:     /********************* submission ****************d*g**/
324: 
325: 
326:     /**
327:      * Tells if the form is anchored.
328:      * @return bool
329:      */
330:     public function isAnchored()
331:     {
332:         return TRUE;
333:     }
334: 
335: 
336:     /**
337:      * Tells if the form was submitted.
338:      * @return ISubmitterControl|FALSE  submittor control
339:      */
340:     public function isSubmitted()
341:     {
342:         if ($this->submittedBy === NULL) {
343:             $this->getHttpData();
344:         }
345:         return $this->submittedBy;
346:     }
347: 
348: 
349:     /**
350:      * Tells if the form was submitted and successfully validated.
351:      * @return bool
352:      */
353:     public function isSuccess()
354:     {
355:         return $this->isSubmitted() && $this->isValid();
356:     }
357: 
358: 
359:     /**
360:      * Sets the submittor control.
361:      * @return self
362:      */
363:     public function setSubmittedBy(ISubmitterControl $by = NULL)
364:     {
365:         $this->submittedBy = $by === NULL ? FALSE : $by;
366:         return $this;
367:     }
368: 
369: 
370:     /**
371:      * Returns submitted HTTP data.
372:      * @return mixed
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:      * Fires submit/click events.
393:      * @return void
394:      */
395:     public function fireEvents()
396:     {
397:         if (!$this->isSubmitted()) {
398:             return;
399:         }
400: 
401:         $this->validate();
402: 
403:         if ($this->submittedBy instanceof ISubmitterControl) {
404:             if ($this->isValid()) {
405:                 $this->submittedBy->onClick($this->submittedBy);
406:             } else {
407:                 $this->submittedBy->onInvalidClick($this->submittedBy);
408:             }
409:         }
410: 
411:         if ($this->onSuccess) {
412:             foreach ($this->onSuccess as $handler) {
413:                 if (!$this->isValid()) {
414:                     $this->onError($this);
415:                     break;
416:                 }
417:                 $params = Nette\Utils\Callback::toReflection($handler)->getParameters();
418:                 $values = isset($params[1]) ? $this->getValues($params[1]->isArray()) : NULL;
419:                 Nette\Utils\Callback::invoke($handler, $this, $values);
420:             }
421:         } elseif (!$this->isValid()) {
422:             $this->onError($this);
423:         }
424:         $this->onSubmit($this);
425:     }
426: 
427: 
428:     /**
429:      * Internal: returns submitted HTTP data or NULL when form was not submitted.
430:      * @return array|NULL
431:      */
432:     protected function receiveHttpData()
433:     {
434:         $httpRequest = $this->getHttpRequest();
435:         if (strcasecmp($this->getMethod(), $httpRequest->getMethod())) {
436:             return;
437:         }
438: 
439:         if ($httpRequest->isMethod('post')) {
440:             $data = Nette\Utils\Arrays::mergeTree($httpRequest->getPost(), $httpRequest->getFiles());
441:         } else {
442:             $data = $httpRequest->getQuery();
443:             if (!$data) {
444:                 return;
445:             }
446:         }
447: 
448:         if ($tracker = $this->getComponent(self::TRACKER_ID, FALSE)) {
449:             if (!isset($data[self::TRACKER_ID]) || $data[self::TRACKER_ID] !== $tracker->getValue()) {
450:                 return;
451:             }
452:         }
453: 
454:         return $data;
455:     }
456: 
457: 
458:     /********************* validation ****************d*g**/
459: 
460: 
461:     public function validate(array $controls = NULL)
462:     {
463:         $this->cleanErrors();
464:         if ($controls === NULL && $this->submittedBy instanceof ISubmitterControl) {
465:             $controls = $this->submittedBy->getValidationScope();
466:         }
467:         $this->validateMaxPostSize();
468:         parent::validate($controls);
469:     }
470: 
471: 
472:     public function validateMaxPostSize()
473:     {
474:         if (!$this->submittedBy || strcasecmp($this->getMethod(), 'POST') || empty($_SERVER['CONTENT_LENGTH'])) {
475:             return;
476:         }
477:         $maxSize = ini_get('post_max_size');
478:         $units = array('k' => 10, 'm' => 20, 'g' => 30);
479:         if (isset($units[$ch = strtolower(substr($maxSize, -1))])) {
480:             $maxSize <<= $units[$ch];
481:         }
482:         if ($maxSize > 0 && $maxSize < $_SERVER['CONTENT_LENGTH']) {
483:             $this->addError(sprintf(Rules::$defaultMessages[self::MAX_FILE_SIZE], $maxSize));
484:         }
485:     }
486: 
487: 
488:     /**
489:      * Adds global error message.
490:      * @param  string  error message
491:      * @return void
492:      */
493:     public function addError($message)
494:     {
495:         $this->errors[] = $message;
496:     }
497: 
498: 
499:     /**
500:      * Returns global validation errors.
501:      * @return array
502:      */
503:     public function getErrors()
504:     {
505:         return array_unique(array_merge($this->errors, parent::getErrors()));
506:     }
507: 
508: 
509:     /**
510:      * @return bool
511:      */
512:     public function hasErrors()
513:     {
514:         return (bool) $this->getErrors();
515:     }
516: 
517: 
518:     /**
519:      * @return void
520:      */
521:     public function cleanErrors()
522:     {
523:         $this->errors = array();
524:     }
525: 
526: 
527:     /**
528:      * Returns form's validation errors.
529:      * @return array
530:      */
531:     public function getOwnErrors()
532:     {
533:         return array_unique($this->errors);
534:     }
535: 
536: 
537:     /** @deprecated */
538:     public function getAllErrors()
539:     {
540:         trigger_error(__METHOD__ . '() is deprecated; use getErrors() instead.', E_USER_DEPRECATED);
541:         return $this->getErrors();
542:     }
543: 
544: 
545:     /********************* rendering ****************d*g**/
546: 
547: 
548:     /**
549:      * Returns form's HTML element template.
550:      * @return Nette\Utils\Html
551:      */
552:     public function getElementPrototype()
553:     {
554:         if (!$this->element) {
555:             $this->element = Nette\Utils\Html::el('form');
556:             $this->element->action = ''; // RFC 1808 -> empty uri means 'this'
557:             $this->element->method = self::POST;
558:         }
559:         return $this->element;
560:     }
561: 
562: 
563:     /**
564:      * Sets form renderer.
565:      * @return self
566:      */
567:     public function setRenderer(IFormRenderer $renderer = NULL)
568:     {
569:         $this->renderer = $renderer;
570:         return $this;
571:     }
572: 
573: 
574:     /**
575:      * Returns form renderer.
576:      * @return IFormRenderer
577:      */
578:     public function getRenderer()
579:     {
580:         if ($this->renderer === NULL) {
581:             $this->renderer = new Rendering\DefaultFormRenderer;
582:         }
583:         return $this->renderer;
584:     }
585: 
586: 
587:     /**
588:      * Renders form.
589:      * @return void
590:      */
591:     public function render()
592:     {
593:         $args = func_get_args();
594:         array_unshift($args, $this);
595:         echo call_user_func_array(array($this->getRenderer(), 'render'), $args);
596:     }
597: 
598: 
599:     /**
600:      * Renders form to string.
601:      * @return can throw exceptions? (hidden parameter)
602:      * @return string
603:      */
604:     public function __toString()
605:     {
606:         try {
607:             return $this->getRenderer()->render($this);
608: 
609:         } catch (\Exception $e) {
610:             if (func_num_args()) {
611:                 throw $e;
612:             }
613:             trigger_error("Exception in " . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
614:         }
615:     }
616: 
617: 
618:     /********************* backend ****************d*g**/
619: 
620: 
621:     /**
622:      * @return Nette\Http\IRequest
623:      */
624:     private function getHttpRequest()
625:     {
626:         if (!$this->httpRequest) {
627:             $factory = new Nette\Http\RequestFactory;
628:             $this->httpRequest = $factory->createHttpRequest();
629:         }
630:         return $this->httpRequest;
631:     }
632: 
633: 
634:     /**
635:      * @return array
636:      */
637:     public function getToggles()
638:     {
639:         $toggles = array();
640:         foreach ($this->getControls() as $control) {
641:             $toggles = $control->getRules()->getToggleStates($toggles);
642:         }
643:         return $toggles;
644:     }
645: 
646: }
647: 
Nette 2.2.2 API API documentation generated by ApiGen 2.8.0