Packages

  • 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

  • NForm
  • NFormContainer
  • NFormGroup
  • NRule
  • NRules

Interfaces

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