PageRenderTime 52ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Symfony/Component/Form/Form.php

https://github.com/rsky/symfony
PHP | 373 lines | 185 code | 45 blank | 143 comment | 27 complexity | fe1476a9c04339c15b934e574557e8ca MD5 | raw file
  1. <?php
  2. namespace Symfony\Component\Form;
  3. /*
  4. * This file is part of the Symfony framework.
  5. *
  6. * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
  7. *
  8. * This source file is subject to the MIT license that is bundled
  9. * with this source code in the file LICENSE.
  10. */
  11. use Symfony\Component\Validator\ValidatorInterface;
  12. /**
  13. * Form represents a form.
  14. *
  15. * A form is composed of a validator schema and a widget form schema.
  16. *
  17. * Form also takes care of Csrf protection by default.
  18. *
  19. * A Csrf secret can be any random string. If set to false, it disables the
  20. * Csrf protection, and if set to null, it forces the form to use the global
  21. * Csrf secret. If the global Csrf secret is also null, then a random one
  22. * is generated on the fly.
  23. *
  24. * @author Fabien Potencier <fabien.potencier@symfony-project.com>
  25. */
  26. class Form extends FieldGroup
  27. {
  28. protected static $defaultCsrfSecret = null;
  29. protected static $defaultCsrfProtection = false;
  30. protected static $defaultCsrfFieldName = '_token';
  31. protected static $defaultLocale = null;
  32. protected $validator = null;
  33. protected $validationGroups = null;
  34. private $csrfSecret = null;
  35. private $csrfFieldName = null;
  36. /**
  37. * Constructor.
  38. *
  39. * @param string $name
  40. * @param array|object $data
  41. * @param ValidatorInterface $validator
  42. * @param array $options
  43. */
  44. public function __construct($name, $data, ValidatorInterface $validator, array $options = array())
  45. {
  46. $this->validator = $validator;
  47. $this->setData($data);
  48. if (self::$defaultCsrfProtection !== false) {
  49. $this->enableCsrfProtection();
  50. }
  51. if (self::$defaultLocale !== null) {
  52. $this->setLocale(self::$defaultLocale);
  53. }
  54. parent::__construct($name, $options);
  55. }
  56. /**
  57. * Sets the validation groups for this form.
  58. *
  59. * @param array|string $validationGroups
  60. */
  61. public function setValidationGroups($validationGroups)
  62. {
  63. $this->validationGroups = $validationGroups === null ? $validationGroups : (array) $validationGroups;
  64. }
  65. /**
  66. * Returns the validation groups for this form.
  67. *
  68. * @return array
  69. */
  70. public function getValidationGroups()
  71. {
  72. return $this->validationGroups;
  73. }
  74. /**
  75. * Sets the default locale for newly created forms.
  76. *
  77. * @param string $defaultLocale
  78. */
  79. static public function setDefaultLocale($defaultLocale)
  80. {
  81. self::$defaultLocale = $defaultLocale;
  82. }
  83. /**
  84. * Returns the default locale for newly created forms.
  85. *
  86. * @return string
  87. */
  88. static public function getDefaultLocale()
  89. {
  90. return self::$defaultLocale;
  91. }
  92. /**
  93. * Binds the form with values and files.
  94. *
  95. * This method is final because it is very easy to break a form when
  96. * overriding this method and adding logic that depends on $taintedFiles.
  97. * You should override doBind() instead where the uploaded files are
  98. * already merged into the data array.
  99. *
  100. * @param array $taintedValues The form data of the $_POST array
  101. * @param array $taintedFiles An array of uploaded files
  102. * @return boolean Whether the form is valid
  103. */
  104. final public function bind($taintedValues, array $taintedFiles = null)
  105. {
  106. if ($taintedFiles === null) {
  107. if ($this->isMultipart() && $this->getParent() === null) {
  108. throw new \InvalidArgumentException('You must provide a files array for multipart forms');
  109. }
  110. $taintedFiles = array();
  111. }
  112. if (null === $taintedValues) {
  113. $taintedValues = array();
  114. }
  115. $this->doBind(self::deepArrayUnion($taintedValues, $taintedFiles));
  116. if ($this->getParent() === null) {
  117. if ($violations = $this->validator->validate($this, $this->getValidationGroups())) {
  118. // TODO: test me
  119. foreach ($violations as $violation) {
  120. $propertyPath = new PropertyPath($violation->getPropertyPath());
  121. $iterator = $propertyPath->getIterator();
  122. if ($iterator->current() == 'data') {
  123. $type = self::DATA_ERROR;
  124. $iterator->next(); // point at the first data element
  125. } else {
  126. $type = self::FIELD_ERROR;
  127. }
  128. $this->addError($violation->getMessageTemplate(), $violation->getMessageParameters(), $iterator, $type);
  129. }
  130. }
  131. }
  132. }
  133. /**
  134. * Binds the form with the given data.
  135. *
  136. * @param array $taintedData The data to bind to the form
  137. * @return boolean Whether the form is valid
  138. */
  139. protected function doBind(array $taintedData)
  140. {
  141. parent::bind($taintedData);
  142. }
  143. /**
  144. * Returns a CSRF token for the given CSRF secret
  145. *
  146. * If you want to change the algorithm used to compute the token, you
  147. * can override this method.
  148. *
  149. * @param string $secret The secret string to use
  150. *
  151. * @return string A token string
  152. */
  153. protected function generateCsrfToken($secret)
  154. {
  155. $sessId = session_id();
  156. if (!$sessId) {
  157. throw new \LogicException('The session must be started in order to generate a proper CSRF Token');
  158. }
  159. return md5($secret.$sessId.get_class($this));
  160. }
  161. /**
  162. * @return true if this form is CSRF protected
  163. */
  164. public function isCsrfProtected()
  165. {
  166. return $this->has($this->getCsrfFieldName());
  167. }
  168. /**
  169. * Enables CSRF protection for this form.
  170. */
  171. public function enableCsrfProtection($csrfFieldName = null, $csrfSecret = null)
  172. {
  173. if (!$this->isCsrfProtected()) {
  174. if ($csrfFieldName === null) {
  175. $csrfFieldName = self::$defaultCsrfFieldName;
  176. }
  177. if ($csrfSecret === null) {
  178. if (self::$defaultCsrfSecret !== null) {
  179. $csrfSecret = self::$defaultCsrfSecret;
  180. } else {
  181. $csrfSecret = md5(__FILE__.php_uname());
  182. }
  183. }
  184. $field = new HiddenField($csrfFieldName, array(
  185. 'property_path' => null,
  186. ));
  187. $field->setData($this->generateCsrfToken($csrfSecret));
  188. $this->add($field);
  189. $this->csrfFieldName = $csrfFieldName;
  190. $this->csrfSecret = $csrfSecret;
  191. }
  192. }
  193. /**
  194. * Disables CSRF protection for this form.
  195. */
  196. public function disableCsrfProtection()
  197. {
  198. if ($this->isCsrfProtected()) {
  199. $this->remove($this->getCsrfFieldName());
  200. $this->csrfFieldName = null;
  201. $this->csrfSecret = null;
  202. }
  203. }
  204. /**
  205. * Returns the CSRF field name used in this form
  206. *
  207. * @return string The CSRF field name
  208. */
  209. public function getCsrfFieldName()
  210. {
  211. return $this->csrfFieldName;
  212. }
  213. /**
  214. * Returns the CSRF secret used in this form
  215. *
  216. * @return string The CSRF secret
  217. */
  218. public function getCsrfSecret()
  219. {
  220. return $this->csrfSecret;
  221. }
  222. /**
  223. * Returns whether the CSRF token is valid
  224. *
  225. * @return boolean
  226. */
  227. public function isCsrfTokenValid()
  228. {
  229. if (!$this->isCsrfProtected()) {
  230. return true;
  231. } else {
  232. return $this->get($this->getCsrfFieldName())->getDisplayedData() === $this->generateCsrfToken($this->getCsrfSecret());
  233. }
  234. }
  235. /**
  236. * Enables CSRF protection for all new forms
  237. */
  238. static public function enableDefaultCsrfProtection()
  239. {
  240. self::$defaultCsrfProtection = true;
  241. }
  242. /**
  243. * Disables Csrf protection for all forms.
  244. */
  245. static public function disableDefaultCsrfProtection()
  246. {
  247. self::$defaultCsrfProtection = false;
  248. }
  249. /**
  250. * Sets the CSRF field name used in all new CSRF protected forms
  251. *
  252. * @param string $name The CSRF field name
  253. */
  254. static public function setDefaultCsrfFieldName($name)
  255. {
  256. self::$defaultCsrfFieldName = $name;
  257. }
  258. /**
  259. * Returns the default CSRF field name
  260. *
  261. * @return string The CSRF field name
  262. */
  263. static public function getDefaultCsrfFieldName()
  264. {
  265. return self::$defaultCsrfFieldName;
  266. }
  267. /**
  268. * Sets the CSRF secret used in all new CSRF protected forms
  269. *
  270. * @param string $secret
  271. */
  272. static public function setDefaultCsrfSecret($secret)
  273. {
  274. self::$defaultCsrfSecret = $secret;
  275. }
  276. /**
  277. * Returns the default CSRF secret
  278. *
  279. * @return string
  280. */
  281. static public function getDefaultCsrfSecret()
  282. {
  283. return self::$defaultCsrfSecret;
  284. }
  285. /**
  286. * Returns whether the maximum POST size was reached in this request.
  287. *
  288. * @return boolean
  289. */
  290. public function isPostMaxSizeReached()
  291. {
  292. if (isset($_SERVER['CONTENT_LENGTH'])) {
  293. $length = (int) $_SERVER['CONTENT_LENGTH'];
  294. $max = trim(ini_get('post_max_size'));
  295. switch (strtolower(substr($max, -1))) {
  296. // The 'G' modifier is available since PHP 5.1.0
  297. case 'g':
  298. $max *= 1024;
  299. case 'm':
  300. $max *= 1024;
  301. case 'k':
  302. $max *= 1024;
  303. }
  304. return $length > $max;
  305. } else {
  306. return false;
  307. }
  308. }
  309. /**
  310. * Merges two arrays without reindexing numeric keys.
  311. *
  312. * @param array $array1 An array to merge
  313. * @param array $array2 An array to merge
  314. *
  315. * @return array The merged array
  316. */
  317. static protected function deepArrayUnion($array1, $array2)
  318. {
  319. foreach ($array2 as $key => $value) {
  320. if (is_array($value) && isset($array1[$key]) && is_array($array1[$key])) {
  321. $array1[$key] = self::deepArrayUnion($array1[$key], $value);
  322. } else {
  323. $array1[$key] = $value;
  324. }
  325. }
  326. return $array1;
  327. }
  328. }