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
  • PHP

Classes

  • Container
  • ControlGroup
  • Form
  • 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:  *
  6:  * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
  7:  *
  8:  * For the full copyright and license information, please view
  9:  * the file license.txt that was distributed with this source code.
 10:  */
 11: 
 12: namespace Nette\Forms;
 13: 
 14: use Nette;
 15: 
 16: 
 17: /**
 18:  * Creates, validates and renders HTML forms.
 19:  *
 20:  * @author     David Grudl
 21:  *
 22:  * @property   mixed $action
 23:  * @property   string $method
 24:  * @property-read array $groups
 25:  * @property   Nette\Localization\ITranslator|NULL $translator
 26:  * @property-read bool $anchored
 27:  * @property-read ISubmitterControl|FALSE $submitted
 28:  * @property-read bool $success
 29:  * @property-read array $httpData
 30:  * @property-read array $errors
 31:  * @property-read Nette\Utils\Html $elementPrototype
 32:  * @property   IFormRenderer $renderer
 33:  */
 34: class Form extends Container
 35: {
 36:     /** validator */
 37:     const EQUAL = ':equal',
 38:         IS_IN = ':equal',
 39:         FILLED = ':filled',
 40:         VALID = ':valid';
 41: 
 42:     // CSRF protection
 43:     const PROTECTION = 'Nette\Forms\Controls\HiddenField::validateEqual';
 44: 
 45:     // button
 46:     const SUBMITTED = ':submitted';
 47: 
 48:     // text
 49:     const MIN_LENGTH = ':minLength',
 50:         MAX_LENGTH = ':maxLength',
 51:         LENGTH = ':length',
 52:         EMAIL = ':email',
 53:         URL = ':url',
 54:         REGEXP = ':regexp',
 55:         PATTERN = ':pattern',
 56:         INTEGER = ':integer',
 57:         NUMERIC = ':integer',
 58:         FLOAT = ':float',
 59:         RANGE = ':range';
 60: 
 61:     // multiselect
 62:     const COUNT = ':length';
 63: 
 64:     // file upload
 65:     const MAX_FILE_SIZE = ':fileSize',
 66:         MIME_TYPE = ':mimeType',
 67:         IMAGE = ':image';
 68: 
 69:     /** method */
 70:     const GET = 'get',
 71:         POST = 'post';
 72: 
 73:     /** @internal tracker ID */
 74:     const TRACKER_ID = '_form_';
 75: 
 76:     /** @internal protection token ID */
 77:     const PROTECTOR_ID = '_token_';
 78: 
 79:     /** @var array of function(Form $sender); Occurs when the form is submitted and successfully validated */
 80:     public $onSuccess;
 81: 
 82:     /** @var array of function(Form $sender); Occurs when the form is submitted and is not valid */
 83:     public $onError;
 84: 
 85:     /** @var array of function(Form $sender); Occurs when the form is submitted */
 86:     public $onSubmit;
 87: 
 88:     /** @deprecated */
 89:     public $onInvalidSubmit;
 90: 
 91:     /** @var mixed or NULL meaning: not detected yet */
 92:     private $submittedBy;
 93: 
 94:     /** @var array */
 95:     private $httpData;
 96: 
 97:     /** @var Html  <form> element */
 98:     private $element;
 99: 
100:     /** @var IFormRenderer */
101:     private $renderer;
102: 
103:     /** @var Nette\Localization\ITranslator */
104:     private $translator;
105: 
106:     /** @var ControlGroup[] */
107:     private $groups = array();
108: 
109:     /** @var array */
110:     private $errors = array();
111: 
112: 
113:     /**
114:      * Form constructor.
115:      * @param  string
116:      */
117:     public function __construct($name = NULL)
118:     {
119:         $this->element = Nette\Utils\Html::el('form');
120:         $this->element->action = ''; // RFC 1808 -> empty uri means 'this'
121:         $this->element->method = self::POST;
122:         $this->element->id = $name === NULL ? NULL : 'frm-' . $name;
123: 
124:         $this->monitor(__CLASS__);
125:         if ($name !== NULL) {
126:             $tracker = new Controls\HiddenField($name);
127:             $tracker->unmonitor(__CLASS__);
128:             $this[self::TRACKER_ID] = $tracker;
129:         }
130:         parent::__construct(NULL, $name);
131:     }
132: 
133: 
134:     /**
135:      * This method will be called when the component (or component's parent)
136:      * becomes attached to a monitored object. Do not call this method yourself.
137:      * @param  Nette\ComponentModel\IComponent
138:      * @return void
139:      */
140:     protected function attached($obj)
141:     {
142:         if ($obj instanceof self) {
143:             throw new Nette\InvalidStateException('Nested forms are forbidden.');
144:         }
145:     }
146: 
147: 
148:     /**
149:      * Returns self.
150:      * @return Form
151:      */
152:     final public function getForm($need = TRUE)
153:     {
154:         return $this;
155:     }
156: 
157: 
158:     /**
159:      * Sets form's action.
160:      * @param  mixed URI
161:      * @return self
162:      */
163:     public function setAction($url)
164:     {
165:         $this->element->action = $url;
166:         return $this;
167:     }
168: 
169: 
170:     /**
171:      * Returns form's action.
172:      * @return mixed URI
173:      */
174:     public function getAction()
175:     {
176:         return $this->element->action;
177:     }
178: 
179: 
180:     /**
181:      * Sets form's method.
182:      * @param  string get | post
183:      * @return self
184:      */
185:     public function setMethod($method)
186:     {
187:         if ($this->httpData !== NULL) {
188:             throw new Nette\InvalidStateException(__METHOD__ . '() must be called until the form is empty.');
189:         }
190:         $this->element->method = strtolower($method);
191:         return $this;
192:     }
193: 
194: 
195:     /**
196:      * Returns form's method.
197:      * @return string get | post
198:      */
199:     public function getMethod()
200:     {
201:         return $this->element->method;
202:     }
203: 
204: 
205:     /**
206:      * Cross-Site Request Forgery (CSRF) form protection.
207:      * @param  string
208:      * @param  int
209:      * @return void
210:      */
211:     public function addProtection($message = NULL, $timeout = NULL)
212:     {
213:         $session = $this->getSession()->getSection('Nette.Forms.Form/CSRF');
214:         $key = "key$timeout";
215:         if (isset($session->$key)) {
216:             $token = $session->$key;
217:         } else {
218:             $session->$key = $token = Nette\Utils\Strings::random();
219:         }
220:         $session->setExpiration($timeout, $key);
221:         $this[self::PROTECTOR_ID] = new Controls\HiddenField($token);
222:         $this[self::PROTECTOR_ID]->addRule(self::PROTECTION, $message, $token);
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 (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|FormGroup
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 FormGroup[]
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:     final 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:     final 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:     final 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 array
371:      */
372:     final public function getHttpData()
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:         return $this->httpData;
383:     }
384: 
385: 
386:     /**
387:      * Fires submit/click events.
388:      * @return void
389:      */
390:     public function fireEvents()
391:     {
392:         if (!$this->isSubmitted()) {
393:             return;
394: 
395:         } elseif ($this->submittedBy instanceof ISubmitterControl) {
396:             if (!$this->submittedBy->getValidationScope() || $this->isValid()) {
397:                 $this->submittedBy->click();
398:                 $valid = TRUE;
399:             } else {
400:                 $this->submittedBy->onInvalidClick($this->submittedBy);
401:             }
402:         }
403: 
404:         if (isset($valid) || $this->isValid()) {
405:             $this->onSuccess($this);
406:         } else {
407:             $this->onError($this);
408:             if ($this->onInvalidSubmit) {
409:                 trigger_error(__CLASS__ . '->onInvalidSubmit is deprecated; use onError instead.', E_USER_WARNING);
410:                 $this->onInvalidSubmit($this);
411:             }
412:         }
413: 
414:         if ($this->onSuccess) { // back compatibility
415:             $this->onSubmit($this);
416:         } elseif ($this->onSubmit) {
417:             trigger_error(__CLASS__ . '->onSubmit changed its behavior; use onSuccess instead.', E_USER_WARNING);
418:             if (isset($valid) || $this->isValid()) {
419:                 $this->onSubmit($this);
420:             }
421:         }
422:     }
423: 
424: 
425:     /**
426:      * Internal: returns submitted HTTP data or NULL when form was not submitted.
427:      * @return array|NULL
428:      */
429:     protected function receiveHttpData()
430:     {
431:         $httpRequest = $this->getHttpRequest();
432:         if (strcasecmp($this->getMethod(), $httpRequest->getMethod())) {
433:             return;
434:         }
435: 
436:         if ($httpRequest->isMethod('post')) {
437:             $data = Nette\Utils\Arrays::mergeTree($httpRequest->getPost(), $httpRequest->getFiles());
438:         } else {
439:             $data = $httpRequest->getQuery();
440:             if (!$data) {
441:                 return;
442:             }
443:         }
444: 
445:         if ($tracker = $this->getComponent(self::TRACKER_ID, FALSE)) {
446:             if (!isset($data[self::TRACKER_ID]) || $data[self::TRACKER_ID] !== $tracker->getValue()) {
447:                 return;
448:             }
449:         }
450: 
451:         return $data;
452:     }
453: 
454: 
455:     /********************* data exchange ****************d*g**/
456: 
457: 
458:     /**
459:      * Returns the values submitted by the form.
460:      * @return Nette\ArrayHash|array
461:      */
462:     public function getValues($asArray = FALSE)
463:     {
464:         $values = parent::getValues($asArray);
465:         unset($values[self::TRACKER_ID], $values[self::PROTECTOR_ID]);
466:         return $values;
467:     }
468: 
469: 
470:     /********************* validation ****************d*g**/
471: 
472: 
473:     /**
474:      * Adds error message to the list.
475:      * @param  string  error message
476:      * @return void
477:      */
478:     public function addError($message)
479:     {
480:         $this->valid = FALSE;
481:         if ($message !== NULL && !in_array($message, $this->errors, TRUE)) {
482:             $this->errors[] = $message;
483:         }
484:     }
485: 
486: 
487:     /**
488:      * Returns validation errors.
489:      * @return array
490:      */
491:     public function getErrors()
492:     {
493:         return $this->errors;
494:     }
495: 
496: 
497:     /**
498:      * @return bool
499:      */
500:     public function hasErrors()
501:     {
502:         return (bool) $this->getErrors();
503:     }
504: 
505: 
506:     /**
507:      * @return void
508:      */
509:     public function cleanErrors()
510:     {
511:         $this->errors = array();
512:         $this->valid = NULL;
513:     }
514: 
515: 
516:     /********************* rendering ****************d*g**/
517: 
518: 
519:     /**
520:      * Returns form's HTML element template.
521:      * @return Nette\Utils\Html
522:      */
523:     public function getElementPrototype()
524:     {
525:         return $this->element;
526:     }
527: 
528: 
529:     /**
530:      * Sets form renderer.
531:      * @return self
532:      */
533:     public function setRenderer(IFormRenderer $renderer)
534:     {
535:         $this->renderer = $renderer;
536:         return $this;
537:     }
538: 
539: 
540:     /**
541:      * Returns form renderer.
542:      * @return IFormRenderer
543:      */
544:     final public function getRenderer()
545:     {
546:         if ($this->renderer === NULL) {
547:             $this->renderer = new Rendering\DefaultFormRenderer;
548:         }
549:         return $this->renderer;
550:     }
551: 
552: 
553:     /**
554:      * Renders form.
555:      * @return void
556:      */
557:     public function render()
558:     {
559:         $args = func_get_args();
560:         array_unshift($args, $this);
561:         echo call_user_func_array(array($this->getRenderer(), 'render'), $args);
562:     }
563: 
564: 
565:     /**
566:      * Renders form to string.
567:      * @return bool  can throw exceptions? (hidden parameter)
568:      * @return string
569:      */
570:     public function __toString()
571:     {
572:         try {
573:             return $this->getRenderer()->render($this);
574: 
575:         } catch (\Exception $e) {
576:             if (func_get_args() && func_get_arg(0)) {
577:                 throw $e;
578:             } else {
579:                 trigger_error("Exception in " . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
580:             }
581:         }
582:     }
583: 
584: 
585:     /********************* backend ****************d*g**/
586: 
587: 
588:     /**
589:      * @return Nette\Http\IRequest
590:      */
591:     protected function getHttpRequest()
592:     {
593:         return Nette\Environment::getHttpRequest();
594:     }
595: 
596: 
597:     /**
598:      * @return Nette\Http\Session
599:      */
600:     protected function getSession()
601:     {
602:         return Nette\Environment::getSession();
603:     }
604: 
605: }
606: 
Nette Framework 2.0.11 API API documentation generated by ApiGen 2.8.0