Namespaces

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

Classes

  • Container
  • ControlGroup
  • Form
  • Rule
  • Rules

Interfaces

  • IControl
  • IFormRenderer
  • ISubmitterControl
  • Overview
  • Namespace
  • Class
  • Tree
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Nette Framework (http://nette.org)
  5:  *
  6:  * Copyright (c) 2004, 2011 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: /**
 19:  * Creates, validates and renders HTML forms.
 20:  *
 21:  * @author     David Grudl
 22:  *
 23:  * @example    forms/basic-example.php  Form definition using fluent interfaces
 24:  * @example    forms/manual-rendering.php  Manual form rendering and separated form and rules definition
 25:  * @example    forms/localization.php  Localization (with Zend_Translate)
 26:  * @example    forms/custom-rendering.php  Custom form rendering
 27:  * @example    forms/custom-validator.php  How to use custom validator
 28:  * @example    forms/naming-containers.php  How to use naming containers
 29:  * @example    forms/CSRF-protection.php  How to use Cross-Site Request Forgery (CSRF) form protection
 30:  *
 31:  * @property   string $action
 32:  * @property   string $method
 33:  * @property-read array $groups
 34:  * @property-read array $httpData
 35:  * @property   Nette\Localization\ITranslator $translator
 36:  * @property-read array $errors
 37:  * @property-read Nette\Utils\Html $elementPrototype
 38:  * @property   IFormRenderer $renderer
 39:  * @property-read bool $submitted
 40:  */
 41: class Form extends Container
 42: {
 43:     /** validator */
 44:     const EQUAL = ':equal',
 45:         IS_IN = ':equal',
 46:         FILLED = ':filled',
 47:         VALID = ':valid';
 48: 
 49:     // CSRF protection
 50:     const PROTECTION = 'Nette\Forms\Controls\HiddenField::validateEqual';
 51: 
 52:     // button
 53:     const SUBMITTED = ':submitted';
 54: 
 55:     // text
 56:     const MIN_LENGTH = ':minLength',
 57:         MAX_LENGTH = ':maxLength',
 58:         LENGTH = ':length',
 59:         EMAIL = ':email',
 60:         URL = ':url',
 61:         REGEXP = ':regexp',
 62:         PATTERN = ':pattern',
 63:         INTEGER = ':integer',
 64:         NUMERIC = ':integer',
 65:         FLOAT = ':float',
 66:         RANGE = ':range';
 67: 
 68:     // file upload
 69:     const MAX_FILE_SIZE = ':fileSize',
 70:         MIME_TYPE = ':mimeType',
 71:         IMAGE = ':image';
 72: 
 73:     /** method */
 74:     const GET = 'get',
 75:         POST = 'post';
 76: 
 77:     /** @internal tracker ID */
 78:     const TRACKER_ID = '_form_';
 79: 
 80:     /** @internal protection token ID */
 81:     const PROTECTOR_ID = '_token_';
 82: 
 83:     /** @var array of function(Form $sender); Occurs when the form is submitted and successfully validated */
 84:     public $onSuccess;
 85: 
 86:     /** @var array of function(Form $sender); Occurs when the form is submitted and is not valid */
 87:     public $onError;
 88: 
 89:     /** @var array of function(Form $sender); Occurs when the form is submitted */
 90:     public $onSubmit;
 91: 
 92:     /** @deprecated */
 93:     public $onInvalidSubmit;
 94: 
 95:     /** @var mixed or NULL meaning: not detected yet */
 96:     private $submittedBy;
 97: 
 98:     /** @var array */
 99:     private $httpData;
100: 
101:     /** @var 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 array of ControlGroup */
111:     private $groups = array();
112: 
113:     /** @var array */
114:     private $errors = array();
115: 
116: 
117: 
118:     /**
119:      * Form constructor.
120:      * @param  string
121:      */
122:     public function __construct($name = NULL)
123:     {
124:         $this->element = Nette\Utils\Html::el('form');
125:         $this->element->action = ''; // RFC 1808 -> empty uri means 'this'
126:         $this->element->method = self::POST;
127:         $this->element->id = 'frm-' . $name;
128: 
129:         $this->monitor(__CLASS__);
130:         if ($name !== NULL) {
131:             $tracker = new Controls\HiddenField($name);
132:             $tracker->unmonitor(__CLASS__);
133:             $this[self::TRACKER_ID] = $tracker;
134:         }
135:         parent::__construct(NULL, $name);
136:     }
137: 
138: 
139: 
140:     /**
141:      * This method will be called when the component (or component's parent)
142:      * becomes attached to a monitored object. Do not call this method yourself.
143:      * @param  IComponent
144:      * @return void
145:      */
146:     protected function attached($obj)
147:     {
148:         if ($obj instanceof self) {
149:             throw new Nette\InvalidStateException('Nested forms are forbidden.');
150:         }
151:     }
152: 
153: 
154: 
155:     /**
156:      * Returns self.
157:      * @return Form
158:      */
159:     final public function getForm($need = TRUE)
160:     {
161:         return $this;
162:     }
163: 
164: 
165: 
166:     /**
167:      * Sets form's action.
168:      * @param  mixed URI
169:      * @return Form  provides a fluent interface
170:      */
171:     public function setAction($url)
172:     {
173:         $this->element->action = $url;
174:         return $this;
175:     }
176: 
177: 
178: 
179:     /**
180:      * Returns form's action.
181:      * @return mixed URI
182:      */
183:     public function getAction()
184:     {
185:         return $this->element->action;
186:     }
187: 
188: 
189: 
190:     /**
191:      * Sets form's method.
192:      * @param  string get | post
193:      * @return Form  provides a fluent interface
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->element->method = strtolower($method);
201:         return $this;
202:     }
203: 
204: 
205: 
206:     /**
207:      * Returns form's method.
208:      * @return string get | post
209:      */
210:     public function getMethod()
211:     {
212:         return $this->element->method;
213:     }
214: 
215: 
216: 
217:     /**
218:      * Cross-Site Request Forgery (CSRF) form protection.
219:      * @param  string
220:      * @param  int
221:      * @return void
222:      */
223:     public function addProtection($message = NULL, $timeout = NULL)
224:     {
225:         $session = $this->getSession()->getSection('Nette.Forms.Form/CSRF');
226:         $key = "key$timeout";
227:         if (isset($session->$key)) {
228:             $token = $session->$key;
229:         } else {
230:             $session->$key = $token = Nette\Utils\Strings::random();
231:         }
232:         $session->setExpiration($timeout, $key);
233:         $this[self::PROTECTOR_ID] = new Controls\HiddenField($token);
234:         $this[self::PROTECTOR_ID]->addRule(self::PROTECTION, $message, $token);
235:     }
236: 
237: 
238: 
239:     /**
240:      * Adds fieldset group to the form.
241:      * @param  string  caption
242:      * @param  bool    set this group as current
243:      * @return ControlGroup
244:      */
245:     public function addGroup($caption = NULL, $setAsCurrent = TRUE)
246:     {
247:         $group = new ControlGroup;
248:         $group->setOption('label', $caption);
249:         $group->setOption('visual', TRUE);
250: 
251:         if ($setAsCurrent) {
252:             $this->setCurrentGroup($group);
253:         }
254: 
255:         if (isset($this->groups[$caption])) {
256:             return $this->groups[] = $group;
257:         } else {
258:             return $this->groups[$caption] = $group;
259:         }
260:     }
261: 
262: 
263: 
264:     /**
265:      * Removes fieldset group from form.
266:      * @param  string|FormGroup
267:      * @return void
268:      */
269:     public function removeGroup($name)
270:     {
271:         if (is_string($name) && isset($this->groups[$name])) {
272:             $group = $this->groups[$name];
273: 
274:         } elseif ($name instanceof ControlGroup && in_array($name, $this->groups, TRUE)) {
275:             $group = $name;
276:             $name = array_search($group, $this->groups, TRUE);
277: 
278:         } else {
279:             throw new Nette\InvalidArgumentException("Group not found in form '$this->name'");
280:         }
281: 
282:         foreach ($group->getControls() as $control) {
283:             $this->removeComponent($control);
284:         }
285: 
286:         unset($this->groups[$name]);
287:     }
288: 
289: 
290: 
291:     /**
292:      * Returns all defined groups.
293:      * @return array of FormGroup
294:      */
295:     public function getGroups()
296:     {
297:         return $this->groups;
298:     }
299: 
300: 
301: 
302:     /**
303:      * Returns the specified group.
304:      * @param  string  name
305:      * @return ControlGroup
306:      */
307:     public function getGroup($name)
308:     {
309:         return isset($this->groups[$name]) ? $this->groups[$name] : NULL;
310:     }
311: 
312: 
313: 
314:     /********************* translator ****************d*g**/
315: 
316: 
317: 
318:     /**
319:      * Sets translate adapter.
320:      * @param  Nette\Localization\ITranslator
321:      * @return Form  provides a fluent interface
322:      */
323:     public function setTranslator(Nette\Localization\ITranslator $translator = NULL)
324:     {
325:         $this->translator = $translator;
326:         return $this;
327:     }
328: 
329: 
330: 
331:     /**
332:      * Returns translate adapter.
333:      * @return Nette\Localization\ITranslator|NULL
334:      */
335:     final public function getTranslator()
336:     {
337:         return $this->translator;
338:     }
339: 
340: 
341: 
342:     /********************* submission ****************d*g**/
343: 
344: 
345: 
346:     /**
347:      * Tells if the form is anchored.
348:      * @return bool
349:      */
350:     public function isAnchored()
351:     {
352:         return TRUE;
353:     }
354: 
355: 
356: 
357:     /**
358:      * Tells if the form was submitted.
359:      * @return ISubmitterControl|FALSE  submittor control
360:      */
361:     final public function isSubmitted()
362:     {
363:         if ($this->submittedBy === NULL) {
364:             $this->getHttpData();
365:             $this->submittedBy = !empty($this->httpData);
366:         }
367:         return $this->submittedBy;
368:     }
369: 
370: 
371: 
372:     /**
373:      * Sets the submittor control.
374:      * @param  ISubmitterControl
375:      * @return Form  provides a fluent interface
376:      */
377:     public function setSubmittedBy(ISubmitterControl $by = NULL)
378:     {
379:         $this->submittedBy = $by === NULL ? FALSE : $by;
380:         return $this;
381:     }
382: 
383: 
384: 
385:     /**
386:      * Returns submitted HTTP data.
387:      * @return array
388:      */
389:     final public function getHttpData()
390:     {
391:         if ($this->httpData === NULL) {
392:             if (!$this->isAnchored()) {
393:                 throw new Nette\InvalidStateException('Form is not anchored and therefore can not determine whether it was submitted.');
394:             }
395:             $this->httpData = (array) $this->receiveHttpData();
396:         }
397:         return $this->httpData;
398:     }
399: 
400: 
401: 
402:     /**
403:      * Fires submit/click events.
404:      * @return void
405:      */
406:     public function fireEvents()
407:     {
408:         if (!$this->isSubmitted()) {
409:             return;
410: 
411:         } elseif ($this->submittedBy instanceof ISubmitterControl) {
412:             if (!$this->submittedBy->getValidationScope() || $this->isValid()) {
413:                 $this->submittedBy->click();
414:                 $valid = TRUE;
415:             } else {
416:                 $this->submittedBy->onInvalidClick($this->submittedBy);
417:             }
418:         }
419: 
420:         if (isset($valid) || $this->isValid()) {
421:             $this->onSuccess($this);
422:         } else {
423:             $this->onError($this);
424:             if ($this->onInvalidSubmit) {
425:                 trigger_error(__CLASS__ . '->onInvalidSubmit is deprecated; use onError instead.', E_USER_WARNING);
426:                 $this->onInvalidSubmit($this);
427:             }
428:         }
429: 
430:         if ($this->onSuccess) { // back compatibility
431:             $this->onSubmit($this);
432:         } elseif ($this->onSubmit) {
433:             trigger_error(__CLASS__ . '->onSubmit changed its behavior; use onSuccess instead.', E_USER_WARNING);
434:             if (isset($valid) || $this->isValid()) {
435:                 $this->onSubmit($this);
436:             }
437:         }
438:     }
439: 
440: 
441: 
442:     /**
443:      * Internal: receives submitted HTTP data.
444:      * @return array
445:      */
446:     protected function receiveHttpData()
447:     {
448:         $httpRequest = $this->getHttpRequest();
449:         if (strcasecmp($this->getMethod(), $httpRequest->getMethod())) {
450:             return;
451:         }
452: 
453:         if ($httpRequest->isMethod('post')) {
454:             $data = Nette\Utils\Arrays::mergeTree($httpRequest->getPost(), $httpRequest->getFiles());
455:         } else {
456:             $data = $httpRequest->getQuery();
457:         }
458: 
459:         if ($tracker = $this->getComponent(self::TRACKER_ID, FALSE)) {
460:             if (!isset($data[self::TRACKER_ID]) || $data[self::TRACKER_ID] !== $tracker->getValue()) {
461:                 return;
462:             }
463:         }
464: 
465:         return $data;
466:     }
467: 
468: 
469: 
470:     /********************* data exchange ****************d*g**/
471: 
472: 
473: 
474:     /**
475:      * Returns the values submitted by the form.
476:      * @return array
477:      */
478:     public function getValues()
479:     {
480:         $values = parent::getValues();
481:         unset($values[self::TRACKER_ID], $values[self::PROTECTOR_ID]);
482:         return $values;
483:     }
484: 
485: 
486: 
487:     /********************* validation ****************d*g**/
488: 
489: 
490: 
491:     /**
492:      * Adds error message to the list.
493:      * @param  string  error message
494:      * @return void
495:      */
496:     public function addError($message)
497:     {
498:         $this->valid = FALSE;
499:         if ($message !== NULL && !in_array($message, $this->errors, TRUE)) {
500:             $this->errors[] = $message;
501:         }
502:     }
503: 
504: 
505: 
506:     /**
507:      * Returns validation errors.
508:      * @return array
509:      */
510:     public function getErrors()
511:     {
512:         return $this->errors;
513:     }
514: 
515: 
516: 
517:     /**
518:      * @return bool
519:      */
520:     public function hasErrors()
521:     {
522:         return (bool) $this->getErrors();
523:     }
524: 
525: 
526: 
527:     /**
528:      * @return void
529:      */
530:     public function cleanErrors()
531:     {
532:         $this->errors = array();
533:         $this->valid = NULL;
534:     }
535: 
536: 
537: 
538:     /********************* rendering ****************d*g**/
539: 
540: 
541: 
542:     /**
543:      * Returns form's HTML element template.
544:      * @return Nette\Utils\Html
545:      */
546:     public function getElementPrototype()
547:     {
548:         return $this->element;
549:     }
550: 
551: 
552: 
553:     /**
554:      * Sets form renderer.
555:      * @param  IFormRenderer
556:      * @return Form  provides a fluent interface
557:      */
558:     public function setRenderer(IFormRenderer $renderer)
559:     {
560:         $this->renderer = $renderer;
561:         return $this;
562:     }
563: 
564: 
565: 
566:     /**
567:      * Returns form renderer.
568:      * @return IFormRenderer
569:      */
570:     final public function getRenderer()
571:     {
572:         if ($this->renderer === NULL) {
573:             $this->renderer = new Rendering\DefaultFormRenderer;
574:         }
575:         return $this->renderer;
576:     }
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:     /**
594:      * Renders form to string.
595:      * @return bool  can throw exceptions? (hidden parameter)
596:      * @return string
597:      */
598:     public function __toString()
599:     {
600:         try {
601:             return $this->getRenderer()->render($this);
602: 
603:         } catch (\Exception $e) {
604:             if (func_get_args() && func_get_arg(0)) {
605:                 throw $e;
606:             } else {
607:                 Nette\Diagnostics\Debugger::toStringException($e);
608:             }
609:         }
610:     }
611: 
612: 
613: 
614:     /********************* backend ****************d*g**/
615: 
616: 
617: 
618:     /**
619:      * @return Nette\Http\IRequest
620:      */
621:     protected function getHttpRequest()
622:     {
623:         return Nette\Environment::getHttpRequest();
624:     }
625: 
626: 
627: 
628:     /**
629:      * @return Nette\Http\Session
630:      */
631:     protected function getSession()
632:     {
633:         return Nette\Environment::getSession();
634:     }
635: 
636: }
637: 
Nette Framework 2.0beta1 API API documentation generated by ApiGen 2.3.0