Namespaces

  • Nette
    • Application
      • Diagnostics
      • Responses
      • Routers
      • UI
    • Caching
      • Storages
    • ComponentModel
    • Config
      • Adapters
      • Extensions
    • Database
      • Diagnostics
      • Drivers
      • Reflection
      • Table
    • DI
      • Diagnostics
    • Diagnostics
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Latte
      • Macros
    • Loaders
    • Localization
    • Mail
    • Reflection
    • Security
      • Diagnostics
    • Templating
    • Utils
      • PhpGenerator
  • NetteModule
  • none

Classes

  • Container
  • ControlGroup
  • Form
  • Rule
  • Rules

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