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