PageRenderTime 45ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/libs/Nette/Forms/Form.php

https://github.com/kaja47/autotweeter
PHP | 636 lines | 288 code | 153 blank | 195 comment | 39 complexity | e833938fba36cec3881c2ad3ea2e13ee MD5 | raw file
  1. <?php
  2. /**
  3. * This file is part of the Nette Framework (http://nette.org)
  4. *
  5. * Copyright (c) 2004, 2011 David Grudl (http://davidgrudl.com)
  6. *
  7. * For the full copyright and license information, please view
  8. * the file license.txt that was distributed with this source code.
  9. */
  10. namespace Nette\Forms;
  11. use Nette;
  12. /**
  13. * Creates, validates and renders HTML forms.
  14. *
  15. * @author David Grudl
  16. *
  17. * @example forms/basic-example.php Form definition using fluent interfaces
  18. * @example forms/manual-rendering.php Manual form rendering and separated form and rules definition
  19. * @example forms/localization.php Localization (with Zend_Translate)
  20. * @example forms/custom-rendering.php Custom form rendering
  21. * @example forms/custom-validator.php How to use custom validator
  22. * @example forms/naming-containers.php How to use naming containers
  23. * @example forms/CSRF-protection.php How to use Cross-Site Request Forgery (CSRF) form protection
  24. *
  25. * @property string $action
  26. * @property string $method
  27. * @property-read array $groups
  28. * @property-read array $httpData
  29. * @property Nette\Localization\ITranslator $translator
  30. * @property-read array $errors
  31. * @property-read Nette\Utils\Html $elementPrototype
  32. * @property IFormRenderer $renderer
  33. * @property-read bool $submitted
  34. */
  35. class Form extends Container
  36. {
  37. /** validator */
  38. const EQUAL = ':equal',
  39. IS_IN = ':equal',
  40. FILLED = ':filled',
  41. VALID = ':valid';
  42. // CSRF protection
  43. const PROTECTION = 'Nette\Forms\Controls\HiddenField::validateEqual';
  44. // button
  45. const SUBMITTED = ':submitted';
  46. // text
  47. const MIN_LENGTH = ':minLength',
  48. MAX_LENGTH = ':maxLength',
  49. LENGTH = ':length',
  50. EMAIL = ':email',
  51. URL = ':url',
  52. REGEXP = ':regexp',
  53. PATTERN = ':pattern',
  54. INTEGER = ':integer',
  55. NUMERIC = ':integer',
  56. FLOAT = ':float',
  57. RANGE = ':range';
  58. // file upload
  59. const MAX_FILE_SIZE = ':fileSize',
  60. MIME_TYPE = ':mimeType',
  61. IMAGE = ':image';
  62. /** method */
  63. const GET = 'get',
  64. POST = 'post';
  65. /** @internal tracker ID */
  66. const TRACKER_ID = '_form_';
  67. /** @internal protection token ID */
  68. const PROTECTOR_ID = '_token_';
  69. /** @var array of function(Form $sender); Occurs when the form is submitted and successfully validated */
  70. public $onSuccess;
  71. /** @var array of function(Form $sender); Occurs when the form is submitted and is not valid */
  72. public $onError;
  73. /** @var array of function(Form $sender); Occurs when the form is submitted */
  74. public $onSubmit;
  75. /** @deprecated */
  76. public $onInvalidSubmit;
  77. /** @var mixed or NULL meaning: not detected yet */
  78. private $submittedBy;
  79. /** @var array */
  80. private $httpData;
  81. /** @var Html <form> element */
  82. private $element;
  83. /** @var IFormRenderer */
  84. private $renderer;
  85. /** @var Nette\Localization\ITranslator */
  86. private $translator;
  87. /** @var array of ControlGroup */
  88. private $groups = array();
  89. /** @var array */
  90. private $errors = array();
  91. /**
  92. * Form constructor.
  93. * @param string
  94. */
  95. public function __construct($name = NULL)
  96. {
  97. $this->element = Nette\Utils\Html::el('form');
  98. $this->element->action = ''; // RFC 1808 -> empty uri means 'this'
  99. $this->element->method = self::POST;
  100. $this->element->id = 'frm-' . $name;
  101. $this->monitor(__CLASS__);
  102. if ($name !== NULL) {
  103. $tracker = new Controls\HiddenField($name);
  104. $tracker->unmonitor(__CLASS__);
  105. $this[self::TRACKER_ID] = $tracker;
  106. }
  107. parent::__construct(NULL, $name);
  108. }
  109. /**
  110. * This method will be called when the component (or component's parent)
  111. * becomes attached to a monitored object. Do not call this method yourself.
  112. * @param IComponent
  113. * @return void
  114. */
  115. protected function attached($obj)
  116. {
  117. if ($obj instanceof self) {
  118. throw new Nette\InvalidStateException('Nested forms are forbidden.');
  119. }
  120. }
  121. /**
  122. * Returns self.
  123. * @return Form
  124. */
  125. final public function getForm($need = TRUE)
  126. {
  127. return $this;
  128. }
  129. /**
  130. * Sets form's action.
  131. * @param mixed URI
  132. * @return Form provides a fluent interface
  133. */
  134. public function setAction($url)
  135. {
  136. $this->element->action = $url;
  137. return $this;
  138. }
  139. /**
  140. * Returns form's action.
  141. * @return mixed URI
  142. */
  143. public function getAction()
  144. {
  145. return $this->element->action;
  146. }
  147. /**
  148. * Sets form's method.
  149. * @param string get | post
  150. * @return Form provides a fluent interface
  151. */
  152. public function setMethod($method)
  153. {
  154. if ($this->httpData !== NULL) {
  155. throw new Nette\InvalidStateException(__METHOD__ . '() must be called until the form is empty.');
  156. }
  157. $this->element->method = strtolower($method);
  158. return $this;
  159. }
  160. /**
  161. * Returns form's method.
  162. * @return string get | post
  163. */
  164. public function getMethod()
  165. {
  166. return $this->element->method;
  167. }
  168. /**
  169. * Cross-Site Request Forgery (CSRF) form protection.
  170. * @param string
  171. * @param int
  172. * @return void
  173. */
  174. public function addProtection($message = NULL, $timeout = NULL)
  175. {
  176. $session = $this->getSession()->getSection('Nette.Forms.Form/CSRF');
  177. $key = "key$timeout";
  178. if (isset($session->$key)) {
  179. $token = $session->$key;
  180. } else {
  181. $session->$key = $token = Nette\Utils\Strings::random();
  182. }
  183. $session->setExpiration($timeout, $key);
  184. $this[self::PROTECTOR_ID] = new Controls\HiddenField($token);
  185. $this[self::PROTECTOR_ID]->addRule(self::PROTECTION, $message, $token);
  186. }
  187. /**
  188. * Adds fieldset group to the form.
  189. * @param string caption
  190. * @param bool set this group as current
  191. * @return ControlGroup
  192. */
  193. public function addGroup($caption = NULL, $setAsCurrent = TRUE)
  194. {
  195. $group = new ControlGroup;
  196. $group->setOption('label', $caption);
  197. $group->setOption('visual', TRUE);
  198. if ($setAsCurrent) {
  199. $this->setCurrentGroup($group);
  200. }
  201. if (isset($this->groups[$caption])) {
  202. return $this->groups[] = $group;
  203. } else {
  204. return $this->groups[$caption] = $group;
  205. }
  206. }
  207. /**
  208. * Removes fieldset group from form.
  209. * @param string|FormGroup
  210. * @return void
  211. */
  212. public function removeGroup($name)
  213. {
  214. if (is_string($name) && isset($this->groups[$name])) {
  215. $group = $this->groups[$name];
  216. } elseif ($name instanceof ControlGroup && in_array($name, $this->groups, TRUE)) {
  217. $group = $name;
  218. $name = array_search($group, $this->groups, TRUE);
  219. } else {
  220. throw new Nette\InvalidArgumentException("Group not found in form '$this->name'");
  221. }
  222. foreach ($group->getControls() as $control) {
  223. $this->removeComponent($control);
  224. }
  225. unset($this->groups[$name]);
  226. }
  227. /**
  228. * Returns all defined groups.
  229. * @return array of FormGroup
  230. */
  231. public function getGroups()
  232. {
  233. return $this->groups;
  234. }
  235. /**
  236. * Returns the specified group.
  237. * @param string name
  238. * @return ControlGroup
  239. */
  240. public function getGroup($name)
  241. {
  242. return isset($this->groups[$name]) ? $this->groups[$name] : NULL;
  243. }
  244. /********************* translator ****************d*g**/
  245. /**
  246. * Sets translate adapter.
  247. * @param Nette\Localization\ITranslator
  248. * @return Form provides a fluent interface
  249. */
  250. public function setTranslator(Nette\Localization\ITranslator $translator = NULL)
  251. {
  252. $this->translator = $translator;
  253. return $this;
  254. }
  255. /**
  256. * Returns translate adapter.
  257. * @return Nette\Localization\ITranslator|NULL
  258. */
  259. final public function getTranslator()
  260. {
  261. return $this->translator;
  262. }
  263. /********************* submission ****************d*g**/
  264. /**
  265. * Tells if the form is anchored.
  266. * @return bool
  267. */
  268. public function isAnchored()
  269. {
  270. return TRUE;
  271. }
  272. /**
  273. * Tells if the form was submitted.
  274. * @return ISubmitterControl|FALSE submittor control
  275. */
  276. final public function isSubmitted()
  277. {
  278. if ($this->submittedBy === NULL && count($this->getControls())) {
  279. $this->getHttpData();
  280. $this->submittedBy = !empty($this->httpData);
  281. }
  282. return $this->submittedBy;
  283. }
  284. /**
  285. * Sets the submittor control.
  286. * @param ISubmitterControl
  287. * @return Form provides a fluent interface
  288. */
  289. public function setSubmittedBy(ISubmitterControl $by = NULL)
  290. {
  291. $this->submittedBy = $by === NULL ? FALSE : $by;
  292. return $this;
  293. }
  294. /**
  295. * Returns submitted HTTP data.
  296. * @return array
  297. */
  298. final public function getHttpData()
  299. {
  300. if ($this->httpData === NULL) {
  301. if (!$this->isAnchored()) {
  302. throw new Nette\InvalidStateException('Form is not anchored and therefore can not determine whether it was submitted.');
  303. }
  304. $this->httpData = (array) $this->receiveHttpData();
  305. }
  306. return $this->httpData;
  307. }
  308. /**
  309. * Fires submit/click events.
  310. * @return void
  311. */
  312. public function fireEvents()
  313. {
  314. if (!$this->isSubmitted()) {
  315. return;
  316. } elseif ($this->submittedBy instanceof ISubmitterControl) {
  317. if (!$this->submittedBy->getValidationScope() || $this->isValid()) {
  318. $this->submittedBy->click();
  319. $valid = TRUE;
  320. } else {
  321. $this->submittedBy->onInvalidClick($this->submittedBy);
  322. }
  323. }
  324. if (isset($valid) || $this->isValid()) {
  325. $this->onSuccess($this);
  326. } else {
  327. $this->onError($this);
  328. if ($this->onInvalidSubmit) {
  329. trigger_error(__CLASS__ . '->onInvalidSubmit is deprecated; use onError instead.', E_USER_WARNING);
  330. $this->onInvalidSubmit($this);
  331. }
  332. }
  333. if ($this->onSuccess) { // back compatibility
  334. $this->onSubmit($this);
  335. } elseif ($this->onSubmit) {
  336. trigger_error(__CLASS__ . '->onSubmit changed its behavior; use onSuccess instead.', E_USER_WARNING);
  337. if (isset($valid) || $this->isValid()) {
  338. $this->onSubmit($this);
  339. }
  340. }
  341. }
  342. /**
  343. * Internal: receives submitted HTTP data.
  344. * @return array
  345. */
  346. protected function receiveHttpData()
  347. {
  348. $httpRequest = $this->getHttpRequest();
  349. if (strcasecmp($this->getMethod(), $httpRequest->getMethod())) {
  350. return;
  351. }
  352. if ($httpRequest->isMethod('post')) {
  353. $data = Nette\Utils\Arrays::mergeTree($httpRequest->getPost(), $httpRequest->getFiles());
  354. } else {
  355. $data = $httpRequest->getQuery();
  356. }
  357. if ($tracker = $this->getComponent(self::TRACKER_ID, FALSE)) {
  358. if (!isset($data[self::TRACKER_ID]) || $data[self::TRACKER_ID] !== $tracker->getValue()) {
  359. return;
  360. }
  361. }
  362. return $data;
  363. }
  364. /********************* data exchange ****************d*g**/
  365. /**
  366. * Returns the values submitted by the form.
  367. * @return array
  368. */
  369. public function getValues()
  370. {
  371. $values = parent::getValues();
  372. unset($values[self::TRACKER_ID], $values[self::PROTECTOR_ID]);
  373. return $values;
  374. }
  375. /********************* validation ****************d*g**/
  376. /**
  377. * Adds error message to the list.
  378. * @param string error message
  379. * @return void
  380. */
  381. public function addError($message)
  382. {
  383. $this->valid = FALSE;
  384. if ($message !== NULL && !in_array($message, $this->errors, TRUE)) {
  385. $this->errors[] = $message;
  386. }
  387. }
  388. /**
  389. * Returns validation errors.
  390. * @return array
  391. */
  392. public function getErrors()
  393. {
  394. return $this->errors;
  395. }
  396. /**
  397. * @return bool
  398. */
  399. public function hasErrors()
  400. {
  401. return (bool) $this->getErrors();
  402. }
  403. /**
  404. * @return void
  405. */
  406. public function cleanErrors()
  407. {
  408. $this->errors = array();
  409. $this->valid = NULL;
  410. }
  411. /********************* rendering ****************d*g**/
  412. /**
  413. * Returns form's HTML element template.
  414. * @return Nette\Utils\Html
  415. */
  416. public function getElementPrototype()
  417. {
  418. return $this->element;
  419. }
  420. /**
  421. * Sets form renderer.
  422. * @param IFormRenderer
  423. * @return Form provides a fluent interface
  424. */
  425. public function setRenderer(IFormRenderer $renderer)
  426. {
  427. $this->renderer = $renderer;
  428. return $this;
  429. }
  430. /**
  431. * Returns form renderer.
  432. * @return IFormRenderer
  433. */
  434. final public function getRenderer()
  435. {
  436. if ($this->renderer === NULL) {
  437. $this->renderer = new Rendering\DefaultFormRenderer;
  438. }
  439. return $this->renderer;
  440. }
  441. /**
  442. * Renders form.
  443. * @return void
  444. */
  445. public function render()
  446. {
  447. $args = func_get_args();
  448. array_unshift($args, $this);
  449. echo call_user_func_array(array($this->getRenderer(), 'render'), $args);
  450. }
  451. /**
  452. * Renders form to string.
  453. * @return bool can throw exceptions? (hidden parameter)
  454. * @return string
  455. */
  456. public function __toString()
  457. {
  458. try {
  459. return $this->getRenderer()->render($this);
  460. } catch (\Exception $e) {
  461. if (func_get_args() && func_get_arg(0)) {
  462. throw $e;
  463. } else {
  464. Nette\Diagnostics\Debugger::toStringException($e);
  465. }
  466. }
  467. }
  468. /********************* backend ****************d*g**/
  469. /**
  470. * @return Nette\Http\IRequest
  471. */
  472. protected function getHttpRequest()
  473. {
  474. return Nette\Environment::getHttpRequest();
  475. }
  476. /**
  477. * @return Nette\Http\Session
  478. */
  479. protected function getSession()
  480. {
  481. return Nette\Environment::getSession();
  482. }
  483. }