Packages

  • Nette
    • Application
    • Caching
    • Collections
    • Config
    • Forms
    • IO
    • Loaders
    • Mail
    • Reflection
    • Security
    • Templates
    • Web
  • None
  • PHP

Classes

  • NButton
  • NCheckbox
  • NConventionalRenderer
  • NFileUpload
  • NForm
  • NFormContainer
  • NFormGroup
  • NHiddenField
  • NImageButton
  • NInstantClientScript
  • NMultiSelectBox
  • NRadioList
  • NRule
  • NRules
  • NSelectBox
  • NSubmitButton
  • NTextArea
  • NTextBase
  • NTextInput

Interfaces

  • IFormControl
  • IFormRenderer
  • INamingContainer
  • ISubmitterControl
  • Overview
  • Package
  • Class
  • Tree
  • Other releases
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Nette Framework (https://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:  * @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:  * @example    forms/custom-encoding.php  How to change charset
 28:  *
 29:  * @property   string $action
 30:  * @property   string $method
 31:  * @property-read array $groups
 32:  * @property-read array $httpData
 33:  * @property   string $encoding
 34:  * @property   ITranslator $translator
 35:  * @property-read array $errors
 36:  * @property-read NHtml $elementPrototype
 37:  * @property   IFormRenderer $renderer
 38:  * @property-read boold $submitted
 39:  * @package Nette\Forms
 40:  */
 41: class NForm extends NFormContainer
 42: {
 43:     /**#@+ operation name */
 44:     const EQUAL = ':equal';
 45:     const IS_IN = ':equal';
 46:     const FILLED = ':filled';
 47:     const VALID = ':valid';
 48: 
 49:     // button
 50:     const SUBMITTED = ':submitted';
 51: 
 52:     // text
 53:     const MIN_LENGTH = ':minLength';
 54:     const MAX_LENGTH = ':maxLength';
 55:     const LENGTH = ':length';
 56:     const EMAIL = ':email';
 57:     const URL = ':url';
 58:     const REGEXP = ':regexp';
 59:     const INTEGER = ':integer';
 60:     const NUMERIC = ':integer';
 61:     const FLOAT = ':float';
 62:     const RANGE = ':range';
 63: 
 64:     // file upload
 65:     const MAX_FILE_SIZE = ':fileSize';
 66:     const MIME_TYPE = ':mimeType';
 67: 
 68:     // special case
 69:     const SCRIPT = 'Nette\\Forms\\InstantClientScript::javascript';
 70:     /**#@-*/
 71: 
 72:     /**#@+ method */
 73:     const GET = 'get';
 74:     const POST = 'post';
 75:     /**#@-*/
 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 $onSubmit;
 85: 
 86:     /** @var array of function(Form $sender); Occurs when the form is submitted and not validated */
 87:     public $onInvalidSubmit;
 88: 
 89:     /** @var mixed or NULL meaning: not detected yet */
 90:     private $submittedBy;
 91: 
 92:     /** @var array */
 93:     private $httpData;
 94: 
 95:     /** @var NHtml  <form> element */
 96:     private $element;
 97: 
 98:     /** @var IFormRenderer */
 99:     private $renderer;
100: 
101:     /** @var ITranslator */
102:     private $translator;
103: 
104:     /** @var array of NFormGroup */
105:     private $groups = array();
106: 
107:     /** @var array */
108:     private $errors = array();
109: 
110:     /** @var array */
111:     private $encoding = 'UTF-8';
112: 
113: 
114: 
115:     /**
116:      * Form constructor.
117:      * @param  string
118:      */
119:     public function __construct($name = NULL)
120:     {
121:         $this->element = NHtml::el('form');
122:         $this->element->action = ''; // RFC 1808 -> empty uri means 'this'
123:         $this->element->method = self::POST;
124:         $this->element->id = 'frm-' . $name;
125: 
126:         $this->monitor(__CLASS__);
127:         if ($name !== NULL) {
128:             $tracker = new NHiddenField($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 InvalidStateException('Nested forms are forbidden.');
147:         }
148:     }
149: 
150: 
151: 
152:     /**
153:      * Returns self.
154:      * @return NForm
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 NForm  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 NForm  provides a fluent interface
191:      */
192:     public function setMethod($method)
193:     {
194:         if ($this->httpData !== NULL) {
195:             throw new 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:      * @deprecated
216:      */
217:     public function addTracker()
218:     {
219:         throw new DeprecatedException(__METHOD__ . '() is deprecated; pass form name to the constructor.');
220:     }
221: 
222: 
223: 
224:     /**
225:      * Cross-Site Request Forgery (CSRF) form protection.
226:      * @param  string
227:      * @param  int
228:      * @return void
229:      */
230:     public function addProtection($message = NULL, $timeout = NULL)
231:     {
232:         $session = $this->getSession()->getNamespace('Nette.Forms.Form/CSRF');
233:         $key = "key$timeout";
234:         if (isset($session->$key)) {
235:             $token = $session->$key;
236:         } else {
237:             $session->$key = $token = md5(uniqid('', TRUE));
238:         }
239:         $session->setExpiration($timeout, $key);
240:         $this[self::PROTECTOR_ID] = new NHiddenField($token);
241:         $this[self::PROTECTOR_ID]->addRule(':equal', empty($message) ? 'Security token did not match. Possible CSRF attack.' : $message, $token);
242:     }
243: 
244: 
245: 
246:     /**
247:      * Adds fieldset group to the form.
248:      * @param  string  caption
249:      * @param  bool    set this group as current
250:      * @return NFormGroup
251:      */
252:     public function addGroup($caption = NULL, $setAsCurrent = TRUE)
253:     {
254:         $group = new NFormGroup;
255:         $group->setOption('label', $caption);
256:         $group->setOption('visual', TRUE);
257: 
258:         if ($setAsCurrent) {
259:             $this->setCurrentGroup($group);
260:         }
261: 
262:         if (isset($this->groups[$caption])) {
263:             return $this->groups[] = $group;
264:         } else {
265:             return $this->groups[$caption] = $group;
266:         }
267:     }
268: 
269: 
270: 
271:     /**
272:      * Removes fieldset group from form.
273:      * @param  string|FormGroup
274:      * @return void
275:      */
276:     public function removeGroup($name)
277:     {
278:         if (is_string($name) && isset($this->groups[$name])) {
279:             $group = $this->groups[$name];
280: 
281:         } elseif ($name instanceof NFormGroup && in_array($name, $this->groups, TRUE)) {
282:             $group = $name;
283:             $name = array_search($group, $this->groups, TRUE);
284: 
285:         } else {
286:             throw new InvalidArgumentException("Group not found in form '$this->name'");
287:         }
288: 
289:         foreach ($group->getControls() as $control) {
290:             $this->removeComponent($control);
291:         }
292: 
293:         unset($this->groups[$name]);
294:     }
295: 
296: 
297: 
298:     /**
299:      * Returns all defined groups.
300:      * @return array of FormGroup
301:      */
302:     public function getGroups()
303:     {
304:         return $this->groups;
305:     }
306: 
307: 
308: 
309:     /**
310:      * Returns the specified group.
311:      * @param  string  name
312:      * @return NFormGroup
313:      */
314:     public function getGroup($name)
315:     {
316:         return isset($this->groups[$name]) ? $this->groups[$name] : NULL;
317:     }
318: 
319: 
320: 
321:     /**
322:      * Set the encoding for the values.
323:      * @param  string
324:      * @return NForm  provides a fluent interface
325:      */
326:     public function setEncoding($value)
327:     {
328:         $this->encoding = empty($value) ? 'UTF-8' : strtoupper($value);
329:         if ($this->encoding !== 'UTF-8' && !extension_loaded('mbstring')) {
330:             throw new Exception("The PHP extension 'mbstring' is required for this encoding but is not loaded.");
331:         }
332:         return $this;
333:     }
334: 
335: 
336: 
337:     /**
338:      * Returns the encoding.
339:      * @return string
340:      */
341:     final public function getEncoding()
342:     {
343:         return $this->encoding;
344:     }
345: 
346: 
347: 
348:     /********************* translator ****************d*g**/
349: 
350: 
351: 
352:     /**
353:      * Sets translate adapter.
354:      * @param  ITranslator
355:      * @return NForm  provides a fluent interface
356:      */
357:     public function setTranslator(ITranslator $translator = NULL)
358:     {
359:         $this->translator = $translator;
360:         return $this;
361:     }
362: 
363: 
364: 
365:     /**
366:      * Returns translate adapter.
367:      * @return ITranslator|NULL
368:      */
369:     final public function getTranslator()
370:     {
371:         return $this->translator;
372:     }
373: 
374: 
375: 
376:     /********************* submission ****************d*g**/
377: 
378: 
379: 
380:     /**
381:      * Tells if the form is anchored.
382:      * @return bool
383:      */
384:     public function isAnchored()
385:     {
386:         return TRUE;
387:     }
388: 
389: 
390: 
391:     /**
392:      * Tells if the form was submitted.
393:      * @return ISubmitterControl|FALSE  submittor control
394:      */
395:     final public function isSubmitted()
396:     {
397:         if ($this->submittedBy === NULL) {
398:             $this->getHttpData();
399:             $this->submittedBy = !empty($this->httpData);
400:         }
401:         return $this->submittedBy;
402:     }
403: 
404: 
405: 
406:     /**
407:      * Sets the submittor control.
408:      * @param  ISubmitterControl
409:      * @return NForm  provides a fluent interface
410:      */
411:     public function setSubmittedBy(ISubmitterControl $by = NULL)
412:     {
413:         $this->submittedBy = $by === NULL ? FALSE : $by;
414:         return $this;
415:     }
416: 
417: 
418: 
419:     /**
420:      * Returns submitted HTTP data.
421:      * @return array
422:      */
423:     final public function getHttpData()
424:     {
425:         if ($this->httpData === NULL) {
426:             if (!$this->isAnchored()) {
427:                 throw new InvalidStateException('Form is not anchored and therefore can not determine whether it was submitted.');
428:             }
429:             $this->httpData = (array) $this->receiveHttpData();
430:         }
431:         return $this->httpData;
432:     }
433: 
434: 
435: 
436:     /**
437:      * Fires submit/click events.
438:      * @return void
439:      */
440:     public function fireEvents()
441:     {
442:         if (!$this->isSubmitted()) {
443:             return;
444: 
445:         } elseif ($this->submittedBy instanceof ISubmitterControl) {
446:             if (!$this->submittedBy->getValidationScope() || $this->isValid()) {
447:                 $this->submittedBy->click();
448:                 $this->onSubmit($this);
449:             } else {
450:                 $this->submittedBy->onInvalidClick($this->submittedBy);
451:                 $this->onInvalidSubmit($this);
452:             }
453: 
454:         } elseif ($this->isValid()) {
455:             $this->onSubmit($this);
456: 
457:         } else {
458:             $this->onInvalidSubmit($this);
459:         }
460:     }
461: 
462: 
463: 
464:     /**
465:      * Internal: receives submitted HTTP data.
466:      * @return array
467:      */
468:     protected function receiveHttpData()
469:     {
470:         $httpRequest = $this->getHttpRequest();
471:         if (strcasecmp($this->getMethod(), $httpRequest->getMethod())) {
472:             return;
473:         }
474: 
475:         $httpRequest->setEncoding($this->encoding);
476:         if ($httpRequest->isMethod('post')) {
477:             $data = NArrayTools::mergeTree($httpRequest->getPost(), $httpRequest->getFiles());
478:         } else {
479:             $data = $httpRequest->getQuery();
480:         }
481: 
482:         if ($tracker = $this->getComponent(self::TRACKER_ID, FALSE)) {
483:             if (!isset($data[self::TRACKER_ID]) || $data[self::TRACKER_ID] !== $tracker->getValue()) {
484:                 return;
485:             }
486:         }
487: 
488:         return $data;
489:     }
490: 
491: 
492: 
493:     /**
494:      * @deprecated
495:      */
496:     public function processHttpRequest()
497:     {
498:         trigger_error(__METHOD__ . '() is deprecated; use fireEvents() instead.', E_USER_WARNING);
499:         $this->fireEvents();
500:     }
501: 
502: 
503: 
504:     /********************* data exchange ****************d*g**/
505: 
506: 
507: 
508:     /**
509:      * Returns the values submitted by the form.
510:      * @return array
511:      */
512:     public function getValues()
513:     {
514:         $values = parent::getValues();
515:         unset($values[self::TRACKER_ID], $values[self::PROTECTOR_ID]);
516:         return $values;
517:     }
518: 
519: 
520: 
521:     /********************* validation ****************d*g**/
522: 
523: 
524: 
525:     /**
526:      * Adds error message to the list.
527:      * @param  string  error message
528:      * @return void
529:      */
530:     public function addError($message)
531:     {
532:         $this->valid = FALSE;
533:         if ($message !== NULL && !in_array($message, $this->errors, TRUE)) {
534:             $this->errors[] = $message;
535:         }
536:     }
537: 
538: 
539: 
540:     /**
541:      * Returns validation errors.
542:      * @return array
543:      */
544:     public function getErrors()
545:     {
546:         return $this->errors;
547:     }
548: 
549: 
550: 
551:     /**
552:      * @return bool
553:      */
554:     public function hasErrors()
555:     {
556:         return (bool) $this->getErrors();
557:     }
558: 
559: 
560: 
561:     /**
562:      * @return void
563:      */
564:     public function cleanErrors()
565:     {
566:         $this->errors = array();
567:         $this->valid = NULL;
568:     }
569: 
570: 
571: 
572:     /********************* rendering ****************d*g**/
573: 
574: 
575: 
576:     /**
577:      * Returns form's HTML element template.
578:      * @return NHtml
579:      */
580:     public function getElementPrototype()
581:     {
582:         return $this->element;
583:     }
584: 
585: 
586: 
587:     /**
588:      * Sets form renderer.
589:      * @param  IFormRenderer
590:      * @return NForm  provides a fluent interface
591:      */
592:     public function setRenderer(IFormRenderer $renderer)
593:     {
594:         $this->renderer = $renderer;
595:         return $this;
596:     }
597: 
598: 
599: 
600:     /**
601:      * Returns form renderer.
602:      * @return IFormRenderer|NULL
603:      */
604:     final public function getRenderer()
605:     {
606:         if ($this->renderer === NULL) {
607:             $this->renderer = new NConventionalRenderer;
608:         }
609:         return $this->renderer;
610:     }
611: 
612: 
613: 
614:     /**
615:      * Renders form.
616:      * @return void
617:      */
618:     public function render()
619:     {
620:         $args = func_get_args();
621:         array_unshift($args, $this);
622:         $s = call_user_func_array(array($this->getRenderer(), 'render'), $args);
623: 
624:         if (strcmp($this->encoding, 'UTF-8')) {
625:             echo mb_convert_encoding($s, 'HTML-ENTITIES', 'UTF-8');
626:         } else {
627:             echo $s;
628:         }
629:     }
630: 
631: 
632: 
633:     /**
634:      * Renders form to string.
635:      * @return bool  can throw exceptions? (hidden parameter)
636:      * @return string
637:      */
638:     public function __toString()
639:     {
640:         try {
641:             if (strcmp($this->encoding, 'UTF-8')) {
642:                 return mb_convert_encoding($this->getRenderer()->render($this), 'HTML-ENTITIES', 'UTF-8');
643:             } else {
644:                 return $this->getRenderer()->render($this);
645:             }
646: 
647:         } catch (Exception $e) {
648:             if (func_get_args() && func_get_arg(0)) {
649:                 throw $e;
650:             } else {
651:                 NDebug::toStringException($e);
652:             }
653:         }
654:     }
655: 
656: 
657: 
658:     /********************* backend ****************d*g**/
659: 
660: 
661: 
662:     /**
663:      * @return IHttpRequest
664:      */
665:     protected function getHttpRequest()
666:     {
667:         return class_exists('NEnvironment') ? NEnvironment::getHttpRequest() : new NHttpRequest;
668:     }
669: 
670: 
671: 
672:     /**
673:      * @return NSession
674:      */
675:     protected function getSession()
676:     {
677:         return NEnvironment::getSession();
678:     }
679: 
680: }
681: 
Nette Framework 0.9.7 (for PHP 5.2) API documentation generated by ApiGen 2.3.0