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