Namespaces

  • Latte
    • Loaders
    • Macros
    • Runtime
  • Nette
    • Application
      • Responses
      • Routers
      • UI
    • Bridges
      • ApplicationDI
      • ApplicationLatte
      • ApplicationTracy
      • CacheDI
      • CacheLatte
      • DatabaseDI
      • DatabaseTracy
      • DITracy
      • FormsDI
      • FormsLatte
      • Framework
      • HttpDI
      • HttpTracy
      • MailDI
      • ReflectionDI
      • SecurityDI
      • SecurityTracy
    • Caching
      • Storages
    • ComponentModel
    • Database
      • Conventions
      • 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
    • Bridges
      • Nette

Classes

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

Interfaces

  • IControl
  • IFormRenderer
  • ISubmitterControl
  • Overview
  • Namespace
  • Class
  • Tree
  • Deprecated
  • Other releases
  • Nette homepage
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Nette Framework (https://nette.org)
  5:  * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
  6:  */
  7: 
  8: namespace Nette\Forms;
  9: 
 10: use Nette;
 11: 
 12: 
 13: /**
 14:  * Creates, validates and renders HTML forms.
 15:  *
 16:  * @property-read array $errors
 17:  * @property-read Nette\Utils\Html $elementPrototype
 18:  */
 19: class Form extends Container implements Nette\Utils\IHtmlString
 20: {
 21:     /** validator */
 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:     /** @deprecated CSRF protection */
 31:     const PROTECTION = Controls\CsrfProtection::PROTECTION;
 32: 
 33:     // button
 34:     const SUBMITTED = ':submitted';
 35: 
 36:     // text
 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:     // multiselect
 51:     const COUNT = self::LENGTH;
 52: 
 53:     // file upload
 54:     const MAX_FILE_SIZE = ':fileSize',
 55:         MIME_TYPE = ':mimeType',
 56:         IMAGE = ':image',
 57:         MAX_POST_SIZE = ':maxPostSize';
 58: 
 59:     /** method */
 60:     const GET = 'get',
 61:         POST = 'post';
 62: 
 63:     /** submitted data types */
 64:     const DATA_TEXT = 1;
 65:     const DATA_LINE = 2;
 66:     const DATA_FILE = 3;
 67:     const DATA_KEYS = 8;
 68: 
 69:     /** @internal tracker ID */
 70:     const TRACKER_ID = '_form_';
 71: 
 72:     /** @internal protection token ID */
 73:     const PROTECTOR_ID = '_token_';
 74: 
 75:     /** @var callable[]  function (Form $sender); Occurs when the form is submitted and successfully validated */
 76:     public $onSuccess;
 77: 
 78:     /** @var callable[]  function (Form $sender); Occurs when the form is submitted and is not valid */
 79:     public $onError;
 80: 
 81:     /** @var callable[]  function (Form $sender); Occurs when the form is submitted */
 82:     public $onSubmit;
 83: 
 84:     /** @var mixed or NULL meaning: not detected yet */
 85:     private $submittedBy;
 86: 
 87:     /** @var array */
 88:     private $httpData;
 89: 
 90:     /** @var Nette\Utils\Html  <form> element */
 91:     private $element;
 92: 
 93:     /** @var IFormRenderer */
 94:     private $renderer;
 95: 
 96:     /** @var Nette\Localization\ITranslator */
 97:     private $translator;
 98: 
 99:     /** @var ControlGroup[] */
100:     private $groups = array();
101: 
102:     /** @var array */
103:     private $errors = array();
104: 
105:     /** @var Nette\Http\IRequest  used only by standalone form */
106:     public $httpRequest;
107: 
108: 
109:     /**
110:      * Form constructor.
111:      * @param  string
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:      * @return void
127:      */
128:     protected function validateParent(Nette\ComponentModel\IContainer $parent)
129:     {
130:         parent::validateParent($parent);
131:         $this->monitor(__CLASS__);
132:     }
133: 
134: 
135:     /**
136:      * This method will be called when the component (or component's parent)
137:      * becomes attached to a monitored object. Do not call this method yourself.
138:      * @param  Nette\ComponentModel\IComponent
139:      * @return void
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:      * Returns self.
151:      * @return self
152:      */
153:     public function getForm($need = TRUE)
154:     {
155:         return $this;
156:     }
157: 
158: 
159:     /**
160:      * Sets form's action.
161:      * @param  mixed URI
162:      * @return self
163:      */
164:     public function setAction($url)
165:     {
166:         $this->getElementPrototype()->action = $url;
167:         return $this;
168:     }
169: 
170: 
171:     /**
172:      * Returns form's action.
173:      * @return mixed URI
174:      */
175:     public function getAction()
176:     {
177:         return $this->getElementPrototype()->action;
178:     }
179: 
180: 
181:     /**
182:      * Sets form's method.
183:      * @param  string get | post
184:      * @return self
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:      * Returns form's method.
198:      * @return string get | post
199:      */
200:     public function getMethod()
201:     {
202:         return $this->getElementPrototype()->method;
203:     }
204: 
205: 
206:     /**
207:      * Cross-Site Request Forgery (CSRF) form protection.
208:      * @param  string
209:      * @return Controls\CsrfProtection
210:      */
211:     public function addProtection($message = NULL)
212:     {
213:         return $this[self::PROTECTOR_ID] = new Controls\CsrfProtection($message);
214:     }
215: 
216: 
217:     /**
218:      * Adds fieldset group to the form.
219:      * @param  string  caption
220:      * @param  bool    set this group as current
221:      * @return ControlGroup
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:      * Removes fieldset group from form.
243:      * @param  string|ControlGroup
244:      * @return void
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:      * Returns all defined groups.
269:      * @return ControlGroup[]
270:      */
271:     public function getGroups()
272:     {
273:         return $this->groups;
274:     }
275: 
276: 
277:     /**
278:      * Returns the specified group.
279:      * @param  string  name
280:      * @return ControlGroup
281:      */
282:     public function getGroup($name)
283:     {
284:         return isset($this->groups[$name]) ? $this->groups[$name] : NULL;
285:     }
286: 
287: 
288:     /********************* translator ****************d*g**/
289: 
290: 
291:     /**
292:      * Sets translate adapter.
293:      * @return self
294:      */
295:     public function setTranslator(Nette\Localization\ITranslator $translator = NULL)
296:     {
297:         $this->translator = $translator;
298:         return $this;
299:     }
300: 
301: 
302:     /**
303:      * Returns translate adapter.
304:      * @return Nette\Localization\ITranslator|NULL
305:      */
306:     public function getTranslator()
307:     {
308:         return $this->translator;
309:     }
310: 
311: 
312:     /********************* submission ****************d*g**/
313: 
314: 
315:     /**
316:      * Tells if the form is anchored.
317:      * @return bool
318:      */
319:     public function isAnchored()
320:     {
321:         return TRUE;
322:     }
323: 
324: 
325:     /**
326:      * Tells if the form was submitted.
327:      * @return ISubmitterControl|FALSE  submittor control
328:      */
329:     public function isSubmitted()
330:     {
331:         if ($this->submittedBy === NULL) {
332:             $this->getHttpData();
333:         }
334:         return $this->submittedBy;
335:     }
336: 
337: 
338:     /**
339:      * Tells if the form was submitted and successfully validated.
340:      * @return bool
341:      */
342:     public function isSuccess()
343:     {
344:         return $this->isSubmitted() && $this->isValid();
345:     }
346: 
347: 
348:     /**
349:      * Sets the submittor control.
350:      * @return self
351:      */
352:     public function setSubmittedBy(ISubmitterControl $by = NULL)
353:     {
354:         $this->submittedBy = $by === NULL ? FALSE : $by;
355:         return $this;
356:     }
357: 
358: 
359:     /**
360:      * Returns submitted HTTP data.
361:      * @return mixed
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:      * Fires submit/click events.
382:      * @return void
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:      * Internal: returns submitted HTTP data or NULL when form was not submitted.
425:      * @return array|NULL
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:     /********************* validation ****************d*g**/
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:     /** @internal */
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:      * Adds global error message.
486:      * @param  string  error message
487:      * @return void
488:      */
489:     public function addError($message)
490:     {
491:         $this->errors[] = $message;
492:     }
493: 
494: 
495:     /**
496:      * Returns global validation errors.
497:      * @return array
498:      */
499:     public function getErrors()
500:     {
501:         return array_unique(array_merge($this->errors, parent::getErrors()));
502:     }
503: 
504: 
505:     /**
506:      * @return bool
507:      */
508:     public function hasErrors()
509:     {
510:         return (bool) $this->getErrors();
511:     }
512: 
513: 
514:     /**
515:      * @return void
516:      */
517:     public function cleanErrors()
518:     {
519:         $this->errors = array();
520:     }
521: 
522: 
523:     /**
524:      * Returns form's validation errors.
525:      * @return array
526:      */
527:     public function getOwnErrors()
528:     {
529:         return array_unique($this->errors);
530:     }
531: 
532: 
533:     /********************* rendering ****************d*g**/
534: 
535: 
536:     /**
537:      * Returns form's HTML element template.
538:      * @return Nette\Utils\Html
539:      */
540:     public function getElementPrototype()
541:     {
542:         if (!$this->element) {
543:             $this->element = Nette\Utils\Html::el('form');
544:             $this->element->action = ''; // RFC 1808 -> empty uri means 'this'
545:             $this->element->method = self::POST;
546:         }
547:         return $this->element;
548:     }
549: 
550: 
551:     /**
552:      * Sets form renderer.
553:      * @return self
554:      */
555:     public function setRenderer(IFormRenderer $renderer = NULL)
556:     {
557:         $this->renderer = $renderer;
558:         return $this;
559:     }
560: 
561: 
562:     /**
563:      * Returns form renderer.
564:      * @return IFormRenderer
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:      * Renders form.
577:      * @return void
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:      * Renders form to string.
589:      * @param can throw exceptions? (hidden parameter)
590:      * @return string
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:     /********************* backend ****************d*g**/
607: 
608: 
609:     /**
610:      * @return Nette\Http\IRequest
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:      * @return array
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: 
Nette 2.3.8 API API documentation generated by ApiGen 2.8.0