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

  • Form
  • FormContainer
  • FormGroup
  • Rule
  • Rules

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