Packages

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

Classes

  • NForm
  • NFormContainer
  • NFormGroup
  • NRule
  • NRules

Interfaces

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