Namespaces

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

Classes

  • Button
  • Checkbox
  • ConventionalRenderer
  • FileUpload
  • Form
  • FormContainer
  • FormControl
  • FormGroup
  • HiddenField
  • ImageButton
  • InstantClientScript
  • MultiSelectBox
  • RadioList
  • Rule
  • Rules
  • SelectBox
  • SubmitButton
  • TextArea
  • TextBase
  • TextInput

Interfaces

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