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